@khanacademy/perseus 3.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 (1210) hide show
  1. package/.babelrc.js +1 -1
  2. package/.eslintrc.js +1 -0
  3. package/CHANGELOG.md +16 -0
  4. package/dist/__tests__/mock-asset-loading-widget.d.ts +59 -0
  5. package/dist/__tests__/mock-asset-loading-widget.js.flow +70 -0
  6. package/dist/__tests__/mock-widget.d.ts +30 -0
  7. package/dist/__tests__/mock-widget.js.flow +39 -0
  8. package/dist/__tests__/test-items/image-item.d.ts +14 -0
  9. package/dist/__tests__/test-items/image-item.js.flow +20 -0
  10. package/dist/__tests__/test-items/input-number-1-item.d.ts +9 -0
  11. package/dist/__tests__/test-items/input-number-1-item.js.flow +15 -0
  12. package/dist/__tests__/test-items/input-number-2-item.d.ts +9 -0
  13. package/dist/__tests__/test-items/input-number-2-item.js.flow +15 -0
  14. package/dist/__tests__/test-items/table-item.d.ts +9 -0
  15. package/dist/__tests__/test-items/table-item.js.flow +15 -0
  16. package/dist/a11y.d.ts +5 -0
  17. package/dist/a11y.js.flow +11 -0
  18. package/dist/all-widgets.d.ts +22 -0
  19. package/dist/all-widgets.js.flow +33 -0
  20. package/dist/article-renderer.d.ts +54 -0
  21. package/dist/article-renderer.js.flow +69 -0
  22. package/dist/asset-context.d.ts +14 -0
  23. package/dist/asset-context.js.flow +15 -0
  24. package/dist/basic-widgets.d.ts +22 -0
  25. package/dist/basic-widgets.js.flow +33 -0
  26. package/dist/components/button-group.d.ts +27 -0
  27. package/dist/components/button-group.js.flow +33 -0
  28. package/dist/components/constants.d.ts +7 -0
  29. package/dist/components/constants.js.flow +13 -0
  30. package/dist/components/fixed-to-responsive.d.ts +2 -0
  31. package/dist/components/fixed-to-responsive.js.flow +8 -0
  32. package/dist/components/graph.d.ts +44 -0
  33. package/dist/components/graph.js.flow +52 -0
  34. package/dist/components/graphie-classes.d.ts +13 -0
  35. package/dist/components/graphie-classes.js.flow +19 -0
  36. package/dist/components/graphie-movables.d.ts +16 -0
  37. package/dist/components/graphie-movables.js.flow +22 -0
  38. package/dist/components/graphie.d.ts +44 -0
  39. package/dist/components/graphie.js.flow +56 -0
  40. package/dist/components/highlighting/highlightable-content.d.ts +39 -0
  41. package/dist/components/highlighting/highlightable-content.js.flow +59 -0
  42. package/dist/components/highlighting/highlights.d.ts +30 -0
  43. package/dist/components/highlighting/highlights.js.flow +54 -0
  44. package/dist/components/highlighting/ranges.d.ts +42 -0
  45. package/dist/components/highlighting/ranges.js.flow +60 -0
  46. package/dist/components/highlighting/types.d.ts +64 -0
  47. package/dist/components/highlighting/types.js.flow +70 -0
  48. package/dist/components/highlighting/ui/highlight-renderer.d.ts +34 -0
  49. package/dist/components/highlighting/ui/highlight-renderer.js.flow +45 -0
  50. package/dist/components/highlighting/ui/highlight-set-renderer.d.ts +34 -0
  51. package/dist/components/highlighting/ui/highlight-set-renderer.js.flow +44 -0
  52. package/dist/components/highlighting/ui/highlight-tooltip.d.ts +16 -0
  53. package/dist/components/highlighting/ui/highlight-tooltip.js.flow +24 -0
  54. package/dist/components/highlighting/ui/highlighting-ui.d.ts +19 -0
  55. package/dist/components/highlighting/ui/highlighting-ui.js.flow +30 -0
  56. package/dist/components/highlighting/ui/selection-tracker.d.ts +47 -0
  57. package/dist/components/highlighting/ui/selection-tracker.js.flow +56 -0
  58. package/dist/components/highlighting/ui/types.d.ts +13 -0
  59. package/dist/components/highlighting/ui/types.js.flow +22 -0
  60. package/dist/components/highlighting/ui/util.d.ts +28 -0
  61. package/dist/components/highlighting/ui/util.js.flow +42 -0
  62. package/dist/components/highlighting/word-indexer.d.ts +28 -0
  63. package/dist/components/highlighting/word-indexer.js.flow +21 -0
  64. package/dist/components/hud.d.ts +15 -0
  65. package/dist/components/hud.js.flow +21 -0
  66. package/dist/components/icon.d.ts +56 -0
  67. package/dist/components/icon.js.flow +31 -0
  68. package/dist/components/image-loader.d.ts +52 -0
  69. package/dist/components/image-loader.js.flow +49 -0
  70. package/dist/components/info-tip/info-tip.d.ts +14 -0
  71. package/dist/components/info-tip/info-tip.js.flow +20 -0
  72. package/dist/components/info-tip.d.ts +18 -0
  73. package/dist/components/info-tip.js.flow +18 -0
  74. package/dist/components/inline-icon.d.ts +10 -0
  75. package/dist/components/inline-icon.js.flow +16 -0
  76. package/dist/components/input-with-examples.d.ts +52 -0
  77. package/dist/components/input-with-examples.js.flow +58 -0
  78. package/dist/components/lint.d.ts +23 -0
  79. package/dist/components/lint.js.flow +29 -0
  80. package/dist/components/math-input.d.ts +40 -0
  81. package/dist/components/math-input.js.flow +46 -0
  82. package/dist/components/math-output.d.ts +23 -0
  83. package/dist/components/math-output.js.flow +29 -0
  84. package/dist/components/multi-button-group.d.ts +29 -0
  85. package/dist/components/multi-button-group.js.flow +35 -0
  86. package/dist/components/number-input.d.ts +34 -0
  87. package/dist/components/number-input.js.flow +42 -0
  88. package/dist/components/prop-check-box.d.ts +2 -0
  89. package/dist/components/prop-check-box.js.flow +8 -0
  90. package/dist/components/range-input.d.ts +14 -0
  91. package/dist/components/range-input.js.flow +20 -0
  92. package/dist/components/simple-keypad-input.d.ts +19 -0
  93. package/dist/components/simple-keypad-input.js.flow +16 -0
  94. package/dist/components/sortable.d.ts +68 -0
  95. package/dist/components/sortable.js.flow +78 -0
  96. package/dist/components/stub-tag-editor.d.ts +25 -0
  97. package/dist/components/stub-tag-editor.js.flow +18 -0
  98. package/dist/components/svg-image.d.ts +74 -0
  99. package/dist/components/svg-image.js.flow +83 -0
  100. package/dist/components/tex-buttons.d.ts +20 -0
  101. package/dist/components/tex-buttons.js.flow +26 -0
  102. package/dist/components/tex.d.ts +25 -0
  103. package/dist/components/tex.js.flow +25 -0
  104. package/dist/components/text-input.d.ts +33 -0
  105. package/dist/components/text-input.js.flow +39 -0
  106. package/dist/components/text-list-editor.d.ts +16 -0
  107. package/dist/components/text-list-editor.js.flow +22 -0
  108. package/dist/components/tooltip.d.ts +32 -0
  109. package/dist/components/tooltip.js.flow +38 -0
  110. package/dist/components/visibility-observer/create-visibility-observer.d.ts +34 -0
  111. package/dist/components/visibility-observer/create-visibility-observer.js.flow +23 -0
  112. package/dist/components/visibility-observer/get-scroll-parent.d.ts +1 -0
  113. package/dist/components/visibility-observer/get-scroll-parent.js.flow +7 -0
  114. package/dist/components/visibility-observer/visibility-observer.d.ts +31 -0
  115. package/dist/components/visibility-observer/visibility-observer.js.flow +36 -0
  116. package/dist/components/zoomable-tex.d.ts +18 -0
  117. package/dist/components/zoomable-tex.js.flow +18 -0
  118. package/dist/components/zoomable.d.ts +64 -0
  119. package/dist/components/zoomable.js.flow +70 -0
  120. package/dist/components.d.ts +16 -0
  121. package/dist/components.js.flow +22 -0
  122. package/dist/definition-context.d.ts +21 -0
  123. package/dist/definition-context.js.flow +25 -0
  124. package/dist/dependencies.d.ts +3 -0
  125. package/dist/dependencies.js.flow +9 -0
  126. package/dist/error-boundary.d.ts +13 -0
  127. package/dist/error-boundary.js.flow +21 -0
  128. package/dist/es/index.js +10880 -14498
  129. package/dist/es/index.js.map +1 -1
  130. package/dist/extra-widgets.d.ts +22 -0
  131. package/dist/extra-widgets.js.flow +33 -0
  132. package/dist/hint-renderer.d.ts +23 -0
  133. package/dist/hint-renderer.js.flow +32 -0
  134. package/dist/hints-renderer.d.ts +29 -0
  135. package/dist/hints-renderer.js.flow +41 -0
  136. package/dist/icon-paths.d.ts +117 -0
  137. package/dist/icon-paths.js.flow +123 -0
  138. package/dist/index.d.ts +103 -2
  139. package/dist/index.js +12443 -15871
  140. package/dist/index.js.flow +133 -2
  141. package/dist/index.js.map +1 -1
  142. package/dist/init.d.ts +14 -0
  143. package/dist/init.js.flow +20 -0
  144. package/dist/interaction-tracker.d.ts +23 -0
  145. package/dist/interaction-tracker.js.flow +33 -0
  146. package/dist/interactive2/__tests__/movable-mock.d.ts +5 -0
  147. package/dist/interactive2/__tests__/movable-mock.js.flow +11 -0
  148. package/dist/interactive2/interactive-util.d.ts +31 -0
  149. package/dist/interactive2/interactive-util.js.flow +50 -0
  150. package/dist/interactive2/movable-helper-methods.d.ts +9 -0
  151. package/dist/interactive2/movable-helper-methods.js.flow +15 -0
  152. package/dist/interactive2/movable-line-options.d.ts +34 -0
  153. package/dist/interactive2/movable-line-options.js.flow +44 -0
  154. package/dist/interactive2/movable-line.d.ts +2 -0
  155. package/dist/interactive2/movable-line.js.flow +8 -0
  156. package/dist/interactive2/movable-point-options.d.ts +34 -0
  157. package/dist/interactive2/movable-point-options.js.flow +44 -0
  158. package/dist/interactive2/movable-point.d.ts +2 -0
  159. package/dist/interactive2/movable-point.js.flow +8 -0
  160. package/dist/interactive2/movable-polygon-options.d.ts +40 -0
  161. package/dist/interactive2/movable-polygon-options.js.flow +47 -0
  162. package/dist/interactive2/movable-polygon.d.ts +2 -0
  163. package/dist/interactive2/movable-polygon.js.flow +8 -0
  164. package/dist/interactive2/movable.d.ts +2 -0
  165. package/dist/interactive2/movable.js.flow +8 -0
  166. package/dist/interactive2/objective_.d.ts +13 -0
  167. package/dist/interactive2/objective_.js.flow +25 -0
  168. package/dist/interactive2/types.d.ts +2 -0
  169. package/dist/interactive2/types.js.flow +8 -0
  170. package/dist/interactive2/wrapped-defaults.d.ts +5 -0
  171. package/dist/interactive2/wrapped-defaults.js.flow +11 -0
  172. package/dist/interactive2/wrapped-ellipse.d.ts +3 -0
  173. package/dist/interactive2/wrapped-ellipse.js.flow +14 -0
  174. package/dist/interactive2/wrapped-line.d.ts +3 -0
  175. package/dist/interactive2/wrapped-line.js.flow +14 -0
  176. package/dist/interactive2/wrapped-path.d.ts +3 -0
  177. package/dist/interactive2/wrapped-path.js.flow +13 -0
  178. package/dist/interactive2.d.ts +10 -0
  179. package/dist/interactive2.js.flow +16 -0
  180. package/dist/item-renderer.d.ts +117 -0
  181. package/dist/item-renderer.js.flow +136 -0
  182. package/dist/item-version.d.ts +4 -0
  183. package/dist/item-version.js.flow +11 -0
  184. package/dist/jipt-paragraphs.d.ts +8 -0
  185. package/dist/jipt-paragraphs.js.flow +14 -0
  186. package/dist/keypad-context.d.ts +20 -0
  187. package/dist/keypad-context.js.flow +18 -0
  188. package/dist/loading-context.d.ts +14 -0
  189. package/dist/loading-context.js.flow +12 -0
  190. package/dist/logging/log.d.ts +55 -0
  191. package/dist/logging/log.js.flow +62 -0
  192. package/dist/mixins/changeable.d.ts +30 -0
  193. package/dist/mixins/changeable.js.flow +33 -0
  194. package/dist/mixins/editor-jsonify.d.ts +4 -0
  195. package/dist/mixins/editor-jsonify.js.flow +10 -0
  196. package/dist/mixins/provide-keypad.d.ts +36 -0
  197. package/dist/mixins/provide-keypad.js.flow +33 -0
  198. package/dist/mixins/widget-jsonify-deprecated.d.ts +5 -0
  199. package/dist/mixins/widget-jsonify-deprecated.js.flow +11 -0
  200. package/dist/mixins/widget-prop-denylist.d.ts +14 -0
  201. package/dist/mixins/widget-prop-denylist.js.flow +20 -0
  202. package/dist/multi-items/item-types.d.ts +32 -0
  203. package/dist/multi-items/items.d.ts +53 -0
  204. package/dist/multi-items/items.js.flow +66 -0
  205. package/dist/multi-items/multi-renderer.d.ts +152 -0
  206. package/dist/multi-items/multi-renderer.js.flow +213 -0
  207. package/dist/multi-items/prop-type-builders.d.ts +20 -0
  208. package/dist/multi-items/prop-type-builders.js.flow +22 -0
  209. package/dist/multi-items/shape-types.d.ts +47 -0
  210. package/dist/multi-items/shape-types.js.flow +60 -0
  211. package/dist/multi-items/shapes.d.ts +23 -0
  212. package/dist/multi-items/shapes.js.flow +25 -0
  213. package/dist/multi-items/tree-types.d.ts +34 -0
  214. package/dist/multi-items/tree-types.js.flow +45 -0
  215. package/dist/multi-items/trees.d.ts +146 -0
  216. package/dist/multi-items/trees.js.flow +147 -0
  217. package/dist/multi-items.d.ts +73 -0
  218. package/dist/multi-items.js.flow +36 -0
  219. package/dist/perseus-api.d.ts +128 -0
  220. package/dist/perseus-api.js.flow +139 -0
  221. package/dist/perseus-env.d.ts +3 -0
  222. package/dist/perseus-env.js.flow +6 -0
  223. package/dist/perseus-error.d.ts +11 -0
  224. package/dist/perseus-error.js.flow +16 -0
  225. package/dist/perseus-markdown.d.ts +11 -0
  226. package/dist/perseus-markdown.js.flow +20 -0
  227. package/dist/perseus-types.d.ts +776 -0
  228. package/dist/perseus-types.js.flow +1032 -0
  229. package/dist/question-paragraph.d.ts +11 -0
  230. package/dist/question-paragraph.js.flow +17 -0
  231. package/dist/renderability.d.ts +10 -0
  232. package/dist/renderability.js.flow +19 -0
  233. package/dist/renderer.d.ts +254 -0
  234. package/dist/renderer.js.flow +327 -0
  235. package/dist/server-item-renderer.d.ts +113 -0
  236. package/dist/server-item-renderer.js.flow +136 -0
  237. package/dist/sigfigs.d.ts +32 -0
  238. package/dist/sigfigs.js.flow +41 -0
  239. package/dist/styles/constants.d.ts +56 -0
  240. package/dist/styles/constants.js.flow +62 -0
  241. package/dist/styles/global-constants.d.ts +342 -0
  242. package/dist/styles/global-constants.js.flow +348 -0
  243. package/dist/styles/global-styles.d.ts +47 -0
  244. package/dist/styles/global-styles.js.flow +53 -0
  245. package/dist/styles/media-queries.d.ts +29 -0
  246. package/dist/styles/media-queries.js.flow +35 -0
  247. package/dist/styles/shared.d.ts +3 -0
  248. package/dist/styles/shared.js.flow +9 -0
  249. package/dist/tex-wrangler.d.ts +7 -0
  250. package/dist/tex-wrangler.js.flow +13 -0
  251. package/dist/translation-linter.d.ts +21 -0
  252. package/dist/translation-linter.js.flow +32 -0
  253. package/dist/traversal.d.ts +15 -0
  254. package/dist/traversal.js.flow +26 -0
  255. package/dist/types.d.ts +262 -0
  256. package/dist/types.js.flow +337 -0
  257. package/dist/util/a11y.d.ts +3 -0
  258. package/dist/util/a11y.js.flow +9 -0
  259. package/dist/util/answer-types.d.ts +34 -0
  260. package/dist/util/answer-types.js.flow +49 -0
  261. package/dist/util/colors.d.ts +89 -0
  262. package/dist/util/colors.js.flow +95 -0
  263. package/dist/util/fix-passage-refs.d.ts +2 -0
  264. package/dist/util/fix-passage-refs.js.flow +8 -0
  265. package/dist/util/graph-utils.d.ts +12 -0
  266. package/dist/util/graph-utils.js.flow +9 -0
  267. package/dist/util/graphie.d.ts +2 -0
  268. package/dist/util/graphie.js.flow +8 -0
  269. package/dist/util/interactive.d.ts +9 -0
  270. package/dist/util/interactive.js.flow +9 -0
  271. package/dist/util/katex-preprocess.d.ts +6 -0
  272. package/dist/util/katex-preprocess.js.flow +12 -0
  273. package/dist/util/math.d.ts +28 -0
  274. package/dist/util/math.js.flow +62 -0
  275. package/dist/util/mobile-native-utils.d.ts +7 -0
  276. package/dist/util/mobile-native-utils.js.flow +12 -0
  277. package/dist/util/react-render.d.ts +2 -0
  278. package/dist/util/react-render.js.flow +12 -0
  279. package/dist/util/register-all-widgets-for-testing.d.ts +1 -0
  280. package/dist/util/register-all-widgets-for-testing.js.flow +7 -0
  281. package/dist/util/scroll-utils.d.ts +10 -0
  282. package/dist/util/scroll-utils.js.flow +19 -0
  283. package/dist/util/sizing-utils.d.ts +9 -0
  284. package/dist/util/sizing-utils.js.flow +20 -0
  285. package/dist/util/tex.d.ts +6 -0
  286. package/dist/util/tex.js.flow +17 -0
  287. package/dist/util/url-utils.d.ts +6 -0
  288. package/dist/util/url-utils.js.flow +11 -0
  289. package/dist/util.d.ts +247 -0
  290. package/dist/util.js.flow +320 -0
  291. package/dist/version.d.ts +11 -0
  292. package/dist/version.js.flow +17 -0
  293. package/dist/widget-container.d.ts +27 -0
  294. package/dist/widget-container.js.flow +33 -0
  295. package/dist/widgets/__tests__/renderQuestion.d.ts +13 -0
  296. package/dist/widgets/__tests__/renderQuestion.js.flow +28 -0
  297. package/dist/widgets/categorizer.d.ts +55 -0
  298. package/dist/widgets/categorizer.js.flow +70 -0
  299. package/dist/widgets/cs-program.d.ts +48 -0
  300. package/dist/widgets/cs-program.js.flow +54 -0
  301. package/dist/widgets/definition.d.ts +42 -0
  302. package/dist/widgets/definition.js.flow +57 -0
  303. package/dist/widgets/dropdown.d.ts +51 -0
  304. package/dist/widgets/dropdown.js.flow +63 -0
  305. package/dist/widgets/explanation.d.ts +49 -0
  306. package/dist/widgets/explanation.js.flow +58 -0
  307. package/dist/widgets/expression.d.ts +90 -0
  308. package/dist/widgets/expression.js.flow +115 -0
  309. package/dist/widgets/graded-group-answer-bar.d.ts +13 -0
  310. package/dist/widgets/graded-group-answer-bar.js.flow +24 -0
  311. package/dist/widgets/graded-group-set.d.ts +52 -0
  312. package/dist/widgets/graded-group-set.js.flow +65 -0
  313. package/dist/widgets/graded-group.d.ts +66 -0
  314. package/dist/widgets/graded-group.js.flow +78 -0
  315. package/dist/widgets/grapher/util.d.ts +19 -0
  316. package/dist/widgets/grapher/util.js.flow +38 -0
  317. package/dist/widgets/grapher.d.ts +63 -0
  318. package/dist/widgets/grapher.js.flow +93 -0
  319. package/dist/widgets/group.d.ts +51 -0
  320. package/dist/widgets/group.js.flow +60 -0
  321. package/dist/widgets/iframe.d.ts +52 -0
  322. package/dist/widgets/iframe.js.flow +53 -0
  323. package/dist/widgets/image.d.ts +59 -0
  324. package/dist/widgets/image.js.flow +73 -0
  325. package/dist/widgets/input-number.d.ts +75 -0
  326. package/dist/widgets/input-number.js.flow +94 -0
  327. package/dist/widgets/interaction/arrow-picker.d.ts +12 -0
  328. package/dist/widgets/interaction/arrow-picker.js.flow +21 -0
  329. package/dist/widgets/interaction/color-picker.d.ts +14 -0
  330. package/dist/widgets/interaction/color-picker.js.flow +23 -0
  331. package/dist/widgets/interaction/dash-picker.d.ts +12 -0
  332. package/dist/widgets/interaction/dash-picker.js.flow +21 -0
  333. package/dist/widgets/interaction/element-container.d.ts +21 -0
  334. package/dist/widgets/interaction/element-container.js.flow +33 -0
  335. package/dist/widgets/interaction.d.ts +51 -0
  336. package/dist/widgets/interaction.js.flow +60 -0
  337. package/dist/widgets/interactive-graph.d.ts +180 -0
  338. package/dist/widgets/interactive-graph.js.flow +242 -0
  339. package/dist/widgets/label-image/answer-choices.d.ts +51 -0
  340. package/dist/widgets/label-image/answer-choices.js.flow +55 -0
  341. package/dist/widgets/label-image/marker.d.ts +28 -0
  342. package/dist/widgets/label-image/marker.js.flow +33 -0
  343. package/dist/widgets/label-image/types.d.ts +14 -0
  344. package/dist/widgets/label-image/types.js.flow +23 -0
  345. package/dist/widgets/label-image.d.ts +113 -0
  346. package/dist/widgets/label-image.js.flow +142 -0
  347. package/dist/widgets/lights-puzzle.d.ts +47 -0
  348. package/dist/widgets/lights-puzzle.js.flow +58 -0
  349. package/dist/widgets/matcher.d.ts +82 -0
  350. package/dist/widgets/matcher.js.flow +101 -0
  351. package/dist/widgets/matrix.d.ts +65 -0
  352. package/dist/widgets/matrix.js.flow +80 -0
  353. package/dist/widgets/measurer.d.ts +21 -0
  354. package/dist/widgets/measurer.js.flow +30 -0
  355. package/dist/widgets/molecule/molecule-drawing.d.ts +5 -0
  356. package/dist/widgets/molecule/molecule-drawing.js.flow +11 -0
  357. package/dist/widgets/molecule/molecule-layout.d.ts +82 -0
  358. package/dist/widgets/molecule/molecule-layout.js.flow +96 -0
  359. package/dist/widgets/molecule/smiles-parser.d.ts +19 -0
  360. package/dist/widgets/molecule/smiles-parser.js.flow +26 -0
  361. package/dist/widgets/molecule.d.ts +51 -0
  362. package/dist/widgets/molecule.js.flow +60 -0
  363. package/dist/widgets/number-line.d.ts +90 -0
  364. package/dist/widgets/number-line.js.flow +102 -0
  365. package/dist/widgets/numeric-input.d.ts +80 -0
  366. package/dist/widgets/numeric-input.js.flow +109 -0
  367. package/dist/widgets/orderer.d.ts +69 -0
  368. package/dist/widgets/orderer.js.flow +81 -0
  369. package/dist/widgets/passage/passage-markdown.d.ts +22 -0
  370. package/dist/widgets/passage/passage-markdown.js.flow +33 -0
  371. package/dist/widgets/passage-ref-target.d.ts +41 -0
  372. package/dist/widgets/passage-ref-target.js.flow +52 -0
  373. package/dist/widgets/passage-ref.d.ts +65 -0
  374. package/dist/widgets/passage-ref.js.flow +85 -0
  375. package/dist/widgets/passage.d.ts +125 -0
  376. package/dist/widgets/passage.js.flow +157 -0
  377. package/dist/widgets/plotter.d.ts +68 -0
  378. package/dist/widgets/plotter.js.flow +78 -0
  379. package/dist/widgets/radio/base-radio.d.ts +38 -0
  380. package/dist/widgets/radio/base-radio.js.flow +44 -0
  381. package/dist/widgets/radio/choice-a11y-text.d.ts +2 -0
  382. package/dist/widgets/radio/choice-a11y-text.js.flow +14 -0
  383. package/dist/widgets/radio/choice-icon/choice-icon.d.ts +16 -0
  384. package/dist/widgets/radio/choice-icon/choice-icon.js.flow +22 -0
  385. package/dist/widgets/radio/choice-icon/cross-out-line.d.ts +9 -0
  386. package/dist/widgets/radio/choice-icon/cross-out-line.js.flow +16 -0
  387. package/dist/widgets/radio/choice-icon/shared-styles.d.ts +5 -0
  388. package/dist/widgets/radio/choice-icon/shared-styles.js.flow +11 -0
  389. package/dist/widgets/radio/choice-none-above.d.ts +6 -0
  390. package/dist/widgets/radio/choice-none-above.js.flow +16 -0
  391. package/dist/widgets/radio/choice.d.ts +23 -0
  392. package/dist/widgets/radio/choice.js.flow +32 -0
  393. package/dist/widgets/radio/choice.jsx.fixture.d.ts +1 -0
  394. package/dist/widgets/radio/choice.jsx.fixture.js.flow +7 -0
  395. package/dist/widgets/radio/focus-ring.d.ts +12 -0
  396. package/dist/widgets/radio/focus-ring.js.flow +15 -0
  397. package/dist/widgets/radio/option-status.d.ts +17 -0
  398. package/dist/widgets/radio/option-status.js.flow +16 -0
  399. package/dist/widgets/radio/util.d.ts +6 -0
  400. package/dist/widgets/radio/util.js.flow +12 -0
  401. package/dist/widgets/radio/widget.d.ts +62 -0
  402. package/dist/widgets/radio/widget.js.flow +80 -0
  403. package/dist/widgets/radio.d.ts +22 -0
  404. package/dist/widgets/radio.js.flow +31 -0
  405. package/dist/widgets/reaction-diagram.d.ts +37 -0
  406. package/dist/widgets/reaction-diagram.js.flow +46 -0
  407. package/dist/widgets/sequence.d.ts +79 -0
  408. package/dist/widgets/sequence.js.flow +100 -0
  409. package/dist/widgets/simulator.d.ts +89 -0
  410. package/dist/widgets/simulator.js.flow +106 -0
  411. package/dist/widgets/sorter.d.ts +82 -0
  412. package/dist/widgets/sorter.js.flow +101 -0
  413. package/dist/widgets/table.d.ts +56 -0
  414. package/dist/widgets/table.js.flow +67 -0
  415. package/dist/widgets/transformer.d.ts +94 -0
  416. package/dist/widgets/transformer.js.flow +127 -0
  417. package/dist/widgets/unit.d.ts +77 -0
  418. package/dist/widgets/unit.js.flow +93 -0
  419. package/dist/widgets/video-transcript-link.d.ts +12 -0
  420. package/dist/widgets/video-transcript-link.js.flow +15 -0
  421. package/dist/widgets/video.d.ts +48 -0
  422. package/dist/widgets/video.js.flow +57 -0
  423. package/dist/widgets.d.ts +73 -0
  424. package/dist/widgets.js.flow +108 -0
  425. package/dist/zoom.d.ts +1 -0
  426. package/dist/zoom.js.flow +7 -0
  427. package/package.json +8 -8
  428. package/src/__stories__/hints-renderer.stories.tsx +58 -0
  429. package/src/__stories__/item-renderer.stories.tsx +29 -0
  430. package/src/__stories__/server-item-renderer.stories.tsx +29 -0
  431. package/src/__testdata__/graphie.testdata.ts +51 -0
  432. package/src/__testdata__/item-renderer.testdata.ts +160 -0
  433. package/src/__testdata__/renderer.testdata.ts +122 -0
  434. package/src/__tests__/a11y.test.ts +303 -0
  435. package/src/__tests__/item-renderer.test.tsx +891 -0
  436. package/src/__tests__/jipt-paragraphs.test.ts +32 -0
  437. package/src/__tests__/mock-asset-loading-widget.tsx +56 -0
  438. package/src/__tests__/mock-widget.tsx +33 -0
  439. package/src/__tests__/perseus-markdown.test.ts +764 -0
  440. package/src/__tests__/renderability.test.ts +480 -0
  441. package/src/__tests__/renderer-api.test.tsx +328 -0
  442. package/src/__tests__/renderer.test.tsx +1802 -0
  443. package/src/__tests__/server-item-renderer.test.tsx +518 -0
  444. package/src/__tests__/test-items/image-item.ts +24 -0
  445. package/src/__tests__/test-items/input-number-1-item.ts +26 -0
  446. package/src/__tests__/test-items/input-number-2-item.ts +38 -0
  447. package/src/__tests__/test-items/table-item.ts +29 -0
  448. package/src/__tests__/tex-wrangler.test.ts +82 -0
  449. package/src/__tests__/util.test.ts +40 -0
  450. package/src/a11y.ts +45 -0
  451. package/src/all-widgets.ts +9 -0
  452. package/src/article-renderer.tsx +215 -0
  453. package/src/asset-context.ts +30 -0
  454. package/src/basic-widgets.ts +16 -0
  455. package/src/components/__stories__/button-group.stories.tsx +59 -0
  456. package/src/components/__stories__/fixed-to-responsive.stories.tsx +143 -0
  457. package/src/components/__stories__/graph.stories.tsx +30 -0
  458. package/src/components/__stories__/graphie.stories.tsx +35 -0
  459. package/src/components/__stories__/hud.stories.tsx +39 -0
  460. package/src/components/__stories__/icon.stories.tsx +63 -0
  461. package/src/components/__stories__/image-loader.stories.tsx +62 -0
  462. package/src/components/__stories__/info-tip.stories.tsx +35 -0
  463. package/src/components/__stories__/inline-icon.stories.tsx +44 -0
  464. package/src/components/__stories__/input-with-examples.stories.tsx +57 -0
  465. package/src/components/__stories__/lint.stories.tsx +51 -0
  466. package/src/components/__stories__/math-input.stories.tsx +34 -0
  467. package/src/components/__stories__/math-output.stories.tsx +25 -0
  468. package/src/components/__stories__/multi-button-group.stories.tsx +70 -0
  469. package/src/components/__stories__/number-input.stories.tsx +43 -0
  470. package/src/components/__stories__/prop-check-box.stories.tsx +65 -0
  471. package/src/components/__stories__/range-input.stories.tsx +31 -0
  472. package/src/components/__stories__/simple-keypad-input.stories.tsx +29 -0
  473. package/src/components/__stories__/sortable.stories.tsx +77 -0
  474. package/src/components/__stories__/stub-tag-editor.stories.tsx +49 -0
  475. package/src/components/__stories__/svg-image.stories.tsx +82 -0
  476. package/src/components/__stories__/tex.stories.tsx +25 -0
  477. package/src/components/__stories__/text-buttons.stories.tsx +66 -0
  478. package/src/components/__stories__/text-input.stories.tsx +39 -0
  479. package/src/components/__stories__/text-list-editor.stories.tsx +34 -0
  480. package/src/components/__stories__/zoomable-tex.stories.tsx +39 -0
  481. package/src/components/__stories__/zoomable.stories.tsx +43 -0
  482. package/src/components/__testdata__/sorter.testdata.ts +19 -0
  483. package/src/components/__tests__/number-input.test.tsx +128 -0
  484. package/src/components/__tests__/sortable.test.tsx +234 -0
  485. package/src/components/__tests__/sorter.test.tsx +111 -0
  486. package/src/components/__tests__/zoomable.test.tsx +399 -0
  487. package/src/components/button-group.tsx +130 -0
  488. package/src/components/constants.ts +14 -0
  489. package/src/components/fixed-to-responsive.tsx +165 -0
  490. package/src/components/graph.tsx +421 -0
  491. package/src/components/graphie-classes.ts +153 -0
  492. package/src/components/graphie-movables.ts +338 -0
  493. package/src/components/graphie.tsx +357 -0
  494. package/src/components/highlighting/highlightable-content.tsx +243 -0
  495. package/src/components/highlighting/highlights.ts +207 -0
  496. package/src/components/highlighting/ranges.ts +304 -0
  497. package/src/components/highlighting/types.ts +73 -0
  498. package/src/components/highlighting/ui/highlight-renderer.tsx +175 -0
  499. package/src/components/highlighting/ui/highlight-set-renderer.tsx +188 -0
  500. package/src/components/highlighting/ui/highlight-tooltip.tsx +141 -0
  501. package/src/components/highlighting/ui/highlighting-ui.tsx +153 -0
  502. package/src/components/highlighting/ui/selection-tracker.tsx +189 -0
  503. package/src/components/highlighting/ui/types.ts +21 -0
  504. package/src/components/highlighting/ui/util.ts +165 -0
  505. package/src/components/highlighting/word-indexer.tsx +156 -0
  506. package/src/components/hud.tsx +178 -0
  507. package/src/components/icon.tsx +140 -0
  508. package/src/components/image-loader.tsx +176 -0
  509. package/src/components/info-tip/info-tip.tsx +144 -0
  510. package/src/components/info-tip.tsx +40 -0
  511. package/src/components/inline-icon.tsx +77 -0
  512. package/src/components/input-with-examples.tsx +249 -0
  513. package/src/components/lint.tsx +453 -0
  514. package/src/components/math-input.tsx +299 -0
  515. package/src/components/math-output.tsx +128 -0
  516. package/src/components/multi-button-group.tsx +135 -0
  517. package/src/components/number-input.tsx +250 -0
  518. package/src/components/prop-check-box.tsx +80 -0
  519. package/src/components/range-input.tsx +60 -0
  520. package/src/components/simple-keypad-input.tsx +88 -0
  521. package/src/components/sortable.tsx +914 -0
  522. package/src/components/stub-tag-editor.tsx +53 -0
  523. package/src/components/svg-image.tsx +777 -0
  524. package/src/components/tex-buttons.tsx +321 -0
  525. package/src/components/tex.tsx +67 -0
  526. package/src/components/text-input.tsx +128 -0
  527. package/src/components/text-list-editor.tsx +169 -0
  528. package/src/components/tooltip.tsx +392 -0
  529. package/src/components/visibility-observer/__stories__/visibility-observer.stories.tsx +143 -0
  530. package/src/components/visibility-observer/create-visibility-observer.ts +108 -0
  531. package/src/components/visibility-observer/get-scroll-parent.ts +46 -0
  532. package/src/components/visibility-observer/visibility-observer.tsx +124 -0
  533. package/src/components/zoomable-tex.tsx +78 -0
  534. package/src/components/zoomable.tsx +300 -0
  535. package/src/components.ts +16 -0
  536. package/src/definition-context.tsx +60 -0
  537. package/src/dependencies.ts +21 -0
  538. package/src/error-boundary.tsx +50 -0
  539. package/src/extra-widgets.ts +75 -0
  540. package/src/hint-renderer.tsx +149 -0
  541. package/src/hints-renderer.tsx +377 -0
  542. package/src/icon-paths.ts +119 -0
  543. package/src/index.ts +158 -0
  544. package/src/init.ts +54 -0
  545. package/src/interaction-tracker.ts +61 -0
  546. package/src/interactive2/__tests__/movable-mock.ts +43 -0
  547. package/src/interactive2/__tests__/movable-point.test.ts +145 -0
  548. package/src/interactive2/interactive-util.ts +155 -0
  549. package/src/interactive2/movable-helper-methods.ts +161 -0
  550. package/src/interactive2/movable-line-options.ts +419 -0
  551. package/src/interactive2/movable-line.ts +318 -0
  552. package/src/interactive2/movable-point-options.ts +212 -0
  553. package/src/interactive2/movable-point.tsx +593 -0
  554. package/src/interactive2/movable-polygon-options.ts +390 -0
  555. package/src/interactive2/movable-polygon.ts +368 -0
  556. package/src/interactive2/movable.ts +275 -0
  557. package/src/interactive2/objective_.ts +79 -0
  558. package/src/interactive2/types.ts +2 -0
  559. package/src/interactive2/wrapped-defaults.ts +87 -0
  560. package/src/interactive2/wrapped-ellipse.ts +94 -0
  561. package/src/interactive2/wrapped-line.ts +116 -0
  562. package/src/interactive2/wrapped-path.ts +51 -0
  563. package/src/interactive2.ts +73 -0
  564. package/src/item-renderer.tsx +514 -0
  565. package/src/item-version.ts +10 -0
  566. package/src/jipt-paragraphs.ts +49 -0
  567. package/src/jquery.mobile.vmouse.js +0 -1
  568. package/src/keypad-context.ts +34 -0
  569. package/src/loading-context.ts +21 -0
  570. package/src/logging/log.test.ts +39 -0
  571. package/src/logging/log.ts +97 -0
  572. package/src/mixins/__tests__/widget-prop-denylist.test.ts +21 -0
  573. package/src/mixins/changeable.ts +133 -0
  574. package/src/mixins/editor-jsonify.ts +13 -0
  575. package/src/mixins/provide-keypad.tsx +144 -0
  576. package/src/mixins/widget-jsonify-deprecated.ts +23 -0
  577. package/src/mixins/widget-prop-denylist.ts +48 -0
  578. package/src/multi-items/__stories__/multi-renderer.stories.tsx +87 -0
  579. package/src/multi-items/__testdata__/multi-renderer.testdata.ts +172 -0
  580. package/src/multi-items/__tests__/items.test.ts +250 -0
  581. package/src/multi-items/__tests__/multi-renderer.test.tsx +910 -0
  582. package/src/multi-items/__tests__/prop-type-builders.test.ts +133 -0
  583. package/src/multi-items/__tests__/shapes.test.ts +43 -0
  584. package/src/multi-items/__tests__/trees.test.ts +325 -0
  585. package/src/multi-items/item-types.js.flow +55 -0
  586. package/src/multi-items/item-types.ts +56 -0
  587. package/src/multi-items/items.ts +176 -0
  588. package/src/multi-items/multi-renderer.tsx +564 -0
  589. package/src/multi-items/prop-type-builders.ts +106 -0
  590. package/src/multi-items/shape-types.ts +52 -0
  591. package/src/multi-items/shapes.ts +52 -0
  592. package/src/multi-items/tree-types.ts +47 -0
  593. package/src/multi-items/trees.ts +360 -0
  594. package/src/multi-items.ts +75 -0
  595. package/src/perseus-api.tsx +188 -0
  596. package/src/perseus-env.ts +27 -0
  597. package/src/perseus-error.ts +17 -0
  598. package/src/perseus-markdown.tsx +407 -0
  599. package/src/perseus-types.ts +1474 -0
  600. package/src/question-paragraph.tsx +29 -0
  601. package/src/renderability.ts +109 -0
  602. package/src/renderer.tsx +1992 -0
  603. package/src/server-item-renderer.tsx +465 -0
  604. package/src/sigfigs.ts +490 -0
  605. package/src/styles/constants.ts +94 -0
  606. package/src/styles/global-constants.ts +343 -0
  607. package/src/styles/global-styles.ts +306 -0
  608. package/src/styles/media-queries.ts +47 -0
  609. package/src/styles/shared.ts +130 -0
  610. package/src/tex-wrangler.ts +182 -0
  611. package/src/translation-linter.ts +126 -0
  612. package/src/traversal.ts +138 -0
  613. package/src/types.ts +468 -0
  614. package/src/util/a11y.ts +23 -0
  615. package/src/util/answer-types.test.ts +114 -0
  616. package/src/util/answer-types.ts +876 -0
  617. package/src/util/colors.ts +93 -0
  618. package/src/util/fix-passage-refs.ts +121 -0
  619. package/src/util/graph-utils.ts +15 -0
  620. package/src/util/graphie.ts +1617 -0
  621. package/src/util/interactive.ts +4191 -0
  622. package/src/util/katex-preprocess.ts +14 -0
  623. package/src/util/math.ts +289 -0
  624. package/src/util/mobile-native-utils.test.ts +22 -0
  625. package/src/util/mobile-native-utils.ts +12 -0
  626. package/src/util/react-render.tsx +27 -0
  627. package/src/util/register-all-widgets-for-testing.ts +11 -0
  628. package/src/util/scroll-utils.ts +155 -0
  629. package/src/util/sizing-utils.ts +46 -0
  630. package/src/util/tex.ts +208 -0
  631. package/src/util/url-utils.ts +34 -0
  632. package/src/util.ts +1003 -0
  633. package/src/version.ts +11 -0
  634. package/src/widget-container.tsx +175 -0
  635. package/src/widgets/__stories__/categorizer.stories.tsx +14 -0
  636. package/src/widgets/__stories__/cs-program.stories.tsx +14 -0
  637. package/src/widgets/__stories__/definition.stories.tsx +111 -0
  638. package/src/widgets/__stories__/dropdown.stories.tsx +14 -0
  639. package/src/widgets/__stories__/explanation.stories.tsx +18 -0
  640. package/src/widgets/__stories__/expression.stories.tsx +29 -0
  641. package/src/widgets/__stories__/graded-group-set.stories.tsx +31 -0
  642. package/src/widgets/__stories__/graded-group.stories.tsx +31 -0
  643. package/src/widgets/__stories__/grapher.stories.tsx +60 -0
  644. package/src/widgets/__stories__/group.stories.tsx +14 -0
  645. package/src/widgets/__stories__/iframe.stories.tsx +14 -0
  646. package/src/widgets/__stories__/image.stories.tsx +61 -0
  647. package/src/widgets/__stories__/input-number.stories.tsx +110 -0
  648. package/src/widgets/__stories__/interaction.stories.tsx +14 -0
  649. package/src/widgets/__stories__/interactive-graph.stories.tsx +60 -0
  650. package/src/widgets/__stories__/matcher.stories.tsx +14 -0
  651. package/src/widgets/__stories__/matrix.stories.tsx +14 -0
  652. package/src/widgets/__stories__/number-line.stories.tsx +14 -0
  653. package/src/widgets/__stories__/numeric-input.stories.tsx +107 -0
  654. package/src/widgets/__stories__/on-screen-keypad.stories.tsx +120 -0
  655. package/src/widgets/__stories__/orderer.stories.tsx +14 -0
  656. package/src/widgets/__stories__/passage-ref.stories.tsx +22 -0
  657. package/src/widgets/__stories__/passage.stories.tsx +32 -0
  658. package/src/widgets/__stories__/radio.stories.tsx +88 -0
  659. package/src/widgets/__stories__/sorter.stories.tsx +14 -0
  660. package/src/widgets/__stories__/transformer.stories.tsx +14 -0
  661. package/src/widgets/__stories__/unit.stories.tsx +30 -0
  662. package/src/widgets/__stories__/video-transcript-link.stories.tsx +27 -0
  663. package/src/widgets/__stories__/video.stories.tsx +14 -0
  664. package/src/widgets/__testdata__/base-radio.testdata.ts +21 -0
  665. package/src/widgets/__testdata__/categorizer.testdata.ts +49 -0
  666. package/src/widgets/__testdata__/cs-program.testdata.ts +27 -0
  667. package/src/widgets/__testdata__/dropdown.testdata.ts +33 -0
  668. package/src/widgets/__testdata__/explanation.testdata.ts +55 -0
  669. package/src/widgets/__testdata__/expression.testdata.ts +94 -0
  670. package/src/widgets/__testdata__/graded-group-set.testdata.ts +356 -0
  671. package/src/widgets/__testdata__/graded-group.testdata.ts +77 -0
  672. package/src/widgets/__testdata__/grapher.testdata.ts +374 -0
  673. package/src/widgets/__testdata__/group.testdata.ts +155 -0
  674. package/src/widgets/__testdata__/iframe.testdata.ts +35 -0
  675. package/src/widgets/__testdata__/image.testdata.ts +36 -0
  676. package/src/widgets/__testdata__/input-number.testdata.ts +72 -0
  677. package/src/widgets/__testdata__/interaction.testdata.ts +262 -0
  678. package/src/widgets/__testdata__/interactive-graph.testdata.ts +644 -0
  679. package/src/widgets/__testdata__/matcher.testdata.ts +33 -0
  680. package/src/widgets/__testdata__/matrix.testdata.ts +28 -0
  681. package/src/widgets/__testdata__/number-line.testdata.ts +30 -0
  682. package/src/widgets/__testdata__/numeric-input.testdata.ts +266 -0
  683. package/src/widgets/__testdata__/orderer.testdata.ts +57 -0
  684. package/src/widgets/__testdata__/passage-ref.testdata.ts +101 -0
  685. package/src/widgets/__testdata__/passage.testdata.ts +95 -0
  686. package/src/widgets/__testdata__/radio.testdata.ts +261 -0
  687. package/src/widgets/__testdata__/sorter.testdata.ts +19 -0
  688. package/src/widgets/__testdata__/transformer.testdata.ts +108 -0
  689. package/src/widgets/__testdata__/video.testdata.ts +21 -0
  690. package/src/widgets/__tests__/categorizer.test.ts +203 -0
  691. package/src/widgets/__tests__/cs-program.test.ts +43 -0
  692. package/src/widgets/__tests__/definition.test.ts +136 -0
  693. package/src/widgets/__tests__/dropdown.test.ts +105 -0
  694. package/src/widgets/__tests__/explanation.test.ts +198 -0
  695. package/src/widgets/__tests__/expression.test.ts +431 -0
  696. package/src/widgets/__tests__/graded-group-set-jipt.test.ts +30 -0
  697. package/src/widgets/__tests__/graded-group-set.test.ts +288 -0
  698. package/src/widgets/__tests__/graded-group.test.ts +232 -0
  699. package/src/widgets/__tests__/grapher.cypress.ts +576 -0
  700. package/src/widgets/__tests__/grapher.test.ts +33 -0
  701. package/src/widgets/__tests__/group.test.tsx +512 -0
  702. package/src/widgets/__tests__/iframe.test.ts +45 -0
  703. package/src/widgets/__tests__/image.test.ts +62 -0
  704. package/src/widgets/__tests__/input-number.test.ts +354 -0
  705. package/src/widgets/__tests__/interaction.test.ts +31 -0
  706. package/src/widgets/__tests__/interactive-graph.cypress.ts +815 -0
  707. package/src/widgets/__tests__/interactive-graph.test.ts +106 -0
  708. package/src/widgets/__tests__/matcher.test.tsx +152 -0
  709. package/src/widgets/__tests__/matrix.test.ts +82 -0
  710. package/src/widgets/__tests__/number-line.test.ts +79 -0
  711. package/src/widgets/__tests__/numeric-input.test.ts +502 -0
  712. package/src/widgets/__tests__/orderer.test.ts +73 -0
  713. package/src/widgets/__tests__/passage-ref.test.ts +168 -0
  714. package/src/widgets/__tests__/passage.test.tsx +224 -0
  715. package/src/widgets/__tests__/radio/base-radio.test.tsx +470 -0
  716. package/src/widgets/__tests__/radio/choice-icon.test.tsx +90 -0
  717. package/src/widgets/__tests__/radio/choice.test.tsx +310 -0
  718. package/src/widgets/__tests__/radio/focus-ring.test.tsx +23 -0
  719. package/src/widgets/__tests__/radio/option-status.test.tsx +77 -0
  720. package/src/widgets/__tests__/radio.test.ts +807 -0
  721. package/src/widgets/__tests__/renderQuestion.tsx +77 -0
  722. package/src/widgets/__tests__/unit.test.tsx +168 -0
  723. package/src/widgets/__tests__/video.test.ts +53 -0
  724. package/src/widgets/categorizer.tsx +343 -0
  725. package/src/widgets/cs-program.tsx +227 -0
  726. package/src/widgets/definition.tsx +121 -0
  727. package/src/widgets/dropdown.tsx +142 -0
  728. package/src/widgets/explanation.tsx +317 -0
  729. package/src/widgets/expression.tsx +647 -0
  730. package/src/widgets/graded-group-answer-bar.tsx +142 -0
  731. package/src/widgets/graded-group-set.tsx +296 -0
  732. package/src/widgets/graded-group.tsx +466 -0
  733. package/src/widgets/grapher/util.tsx +803 -0
  734. package/src/widgets/grapher.tsx +674 -0
  735. package/src/widgets/group.tsx +196 -0
  736. package/src/widgets/iframe.tsx +195 -0
  737. package/src/widgets/image.tsx +309 -0
  738. package/src/widgets/input-number.tsx +413 -0
  739. package/src/widgets/interaction/arrow-picker.tsx +38 -0
  740. package/src/widgets/interaction/color-picker.tsx +69 -0
  741. package/src/widgets/interaction/dash-picker.tsx +45 -0
  742. package/src/widgets/interaction/element-container.tsx +101 -0
  743. package/src/widgets/interaction.tsx +826 -0
  744. package/src/widgets/interactive-graph.tsx +2921 -0
  745. package/src/widgets/label-image/__stories__/answer-choices.stories.tsx +98 -0
  746. package/src/widgets/label-image/__stories__/marker.stories.tsx +181 -0
  747. package/src/widgets/label-image/answer-choices.tsx +300 -0
  748. package/src/widgets/label-image/marker.tsx +364 -0
  749. package/src/widgets/label-image/types.ts +26 -0
  750. package/src/widgets/label-image.test.ts +840 -0
  751. package/src/widgets/label-image.tsx +849 -0
  752. package/src/widgets/lights-puzzle.tsx +359 -0
  753. package/src/widgets/matcher.tsx +278 -0
  754. package/src/widgets/matrix.tsx +637 -0
  755. package/src/widgets/measurer.tsx +217 -0
  756. package/src/widgets/molecule/__tests__/layout.test.ts +162 -0
  757. package/src/widgets/molecule/__tests__/parser.test.ts +155 -0
  758. package/src/widgets/molecule/molecule-drawing.ts +173 -0
  759. package/src/widgets/molecule/molecule-layout.ts +362 -0
  760. package/src/widgets/molecule/smiles-parser.ts +296 -0
  761. package/src/widgets/molecule.tsx +172 -0
  762. package/src/widgets/number-line.tsx +864 -0
  763. package/src/widgets/numeric-input.tsx +563 -0
  764. package/src/widgets/orderer.tsx +788 -0
  765. package/src/widgets/passage/__tests__/passage-markdown.test.ts +376 -0
  766. package/src/widgets/passage/passage-markdown.tsx +475 -0
  767. package/src/widgets/passage-ref-target.tsx +73 -0
  768. package/src/widgets/passage-ref.tsx +215 -0
  769. package/src/widgets/passage.tsx +584 -0
  770. package/src/widgets/plotter.tsx +1204 -0
  771. package/src/widgets/radio/__stories__/base-radio.stories.tsx +204 -0
  772. package/src/widgets/radio/__stories__/choice-icon.stories.tsx +168 -0
  773. package/src/widgets/radio/__stories__/choice-none-above.stories.tsx +40 -0
  774. package/src/widgets/radio/__stories__/choice.stories.tsx +137 -0
  775. package/src/widgets/radio/__stories__/focus-ring.stories.tsx +43 -0
  776. package/src/widgets/radio/__stories__/option-status.stories.tsx +105 -0
  777. package/src/widgets/radio/base-radio.tsx +509 -0
  778. package/src/widgets/radio/choice-a11y-text.ts +61 -0
  779. package/src/widgets/radio/choice-icon/choice-icon.tsx +217 -0
  780. package/src/widgets/radio/choice-icon/cross-out-line.tsx +53 -0
  781. package/src/widgets/radio/choice-icon/shared-styles.ts +15 -0
  782. package/src/widgets/radio/choice-none-above.tsx +56 -0
  783. package/src/widgets/radio/choice.jsx.fixture.ts +45 -0
  784. package/src/widgets/radio/choice.tsx +358 -0
  785. package/src/widgets/radio/focus-ring.tsx +57 -0
  786. package/src/widgets/radio/option-status.tsx +100 -0
  787. package/src/widgets/radio/util.test.ts +18 -0
  788. package/src/widgets/radio/util.ts +31 -0
  789. package/src/widgets/radio/widget.tsx +521 -0
  790. package/src/widgets/radio.ts +157 -0
  791. package/src/widgets/reaction-diagram.tsx +144 -0
  792. package/src/widgets/sequence.tsx +137 -0
  793. package/src/widgets/simulator.tsx +831 -0
  794. package/src/widgets/sorter.tsx +154 -0
  795. package/src/widgets/table.tsx +393 -0
  796. package/src/widgets/transformer.tsx +2951 -0
  797. package/src/widgets/unit.tsx +374 -0
  798. package/src/widgets/video-transcript-link.test.tsx +213 -0
  799. package/src/widgets/video-transcript-link.tsx +84 -0
  800. package/src/widgets/video.tsx +124 -0
  801. package/src/widgets.ts +444 -0
  802. package/src/zoom.ts +558 -0
  803. package/tsconfig.json +25 -0
  804. package/tsconfig.tsbuildinfo +1 -0
  805. package/src/__stories__/hints-renderer.stories.jsx +0 -60
  806. package/src/__stories__/item-renderer.stories.jsx +0 -27
  807. package/src/__stories__/server-item-renderer.stories.jsx +0 -27
  808. package/src/__testdata__/graphie_testdata.js +0 -53
  809. package/src/__testdata__/item-renderer_testdata.js +0 -161
  810. package/src/__testdata__/renderer_testdata.js +0 -123
  811. package/src/__tests__/a11y_test.jsx +0 -304
  812. package/src/__tests__/item-renderer_test.jsx +0 -892
  813. package/src/__tests__/jipt-paragraphs_test.js +0 -34
  814. package/src/__tests__/mock-asset-loading-widget.jsx +0 -57
  815. package/src/__tests__/mock-widget.jsx +0 -35
  816. package/src/__tests__/perseus-markdown_test.jsx +0 -747
  817. package/src/__tests__/renderability_test.jsx +0 -481
  818. package/src/__tests__/renderer-api_test.jsx +0 -309
  819. package/src/__tests__/renderer_test.jsx +0 -1792
  820. package/src/__tests__/server-item-renderer_test.jsx +0 -516
  821. package/src/__tests__/test-items/image-item.js +0 -25
  822. package/src/__tests__/test-items/input-number-1-item.js +0 -27
  823. package/src/__tests__/test-items/input-number-2-item.js +0 -39
  824. package/src/__tests__/test-items/table-item.js +0 -30
  825. package/src/__tests__/tex-wrangler_test.js +0 -83
  826. package/src/__tests__/util_test.js +0 -41
  827. package/src/a11y.js +0 -46
  828. package/src/all-widgets.js +0 -9
  829. package/src/article-renderer.jsx +0 -214
  830. package/src/asset-context.js +0 -30
  831. package/src/basic-widgets.js +0 -13
  832. package/src/components/__stories__/button-group.stories.jsx +0 -61
  833. package/src/components/__stories__/fixed-to-responsive.stories.jsx +0 -132
  834. package/src/components/__stories__/graph.stories.jsx +0 -27
  835. package/src/components/__stories__/graphie.stories.jsx +0 -32
  836. package/src/components/__stories__/hud.stories.jsx +0 -36
  837. package/src/components/__stories__/icon.stories.jsx +0 -56
  838. package/src/components/__stories__/image-loader.stories.jsx +0 -60
  839. package/src/components/__stories__/info-tip.stories.jsx +0 -33
  840. package/src/components/__stories__/inline-icon.stories.jsx +0 -39
  841. package/src/components/__stories__/input-with-examples.stories.jsx +0 -52
  842. package/src/components/__stories__/lint.stories.jsx +0 -40
  843. package/src/components/__stories__/math-input.stories.jsx +0 -29
  844. package/src/components/__stories__/math-output.stories.jsx +0 -24
  845. package/src/components/__stories__/multi-button-group.stories.jsx +0 -69
  846. package/src/components/__stories__/number-input.stories.jsx +0 -42
  847. package/src/components/__stories__/prop-check-box.stories.jsx +0 -62
  848. package/src/components/__stories__/range-input.stories.jsx +0 -28
  849. package/src/components/__stories__/simple-keypad-input.stories.jsx +0 -28
  850. package/src/components/__stories__/sortable.stories.jsx +0 -73
  851. package/src/components/__stories__/stub-tag-editor.stories.jsx +0 -44
  852. package/src/components/__stories__/svg-image.stories.jsx +0 -71
  853. package/src/components/__stories__/tex.stories.jsx +0 -24
  854. package/src/components/__stories__/text-buttons.stories.jsx +0 -51
  855. package/src/components/__stories__/text-input.stories.jsx +0 -34
  856. package/src/components/__stories__/text-list-editor.stories.jsx +0 -33
  857. package/src/components/__stories__/zoomable-tex.stories.jsx +0 -39
  858. package/src/components/__stories__/zoomable.stories.jsx +0 -40
  859. package/src/components/__testdata__/sorter_testdata.js +0 -20
  860. package/src/components/__tests__/number-input_test.jsx +0 -112
  861. package/src/components/__tests__/sortable_test.jsx +0 -234
  862. package/src/components/__tests__/sorter_test.jsx +0 -111
  863. package/src/components/__tests__/zoomable_test.jsx +0 -388
  864. package/src/components/button-group.jsx +0 -131
  865. package/src/components/constants.js +0 -15
  866. package/src/components/fixed-to-responsive.jsx +0 -165
  867. package/src/components/graph.jsx +0 -413
  868. package/src/components/graphie-classes.jsx +0 -148
  869. package/src/components/graphie-movables.jsx +0 -342
  870. package/src/components/graphie.jsx +0 -353
  871. package/src/components/highlighting/highlightable-content.jsx +0 -245
  872. package/src/components/highlighting/highlights.js +0 -208
  873. package/src/components/highlighting/ranges.js +0 -299
  874. package/src/components/highlighting/types.js +0 -73
  875. package/src/components/highlighting/ui/highlight-renderer.jsx +0 -179
  876. package/src/components/highlighting/ui/highlight-set-renderer.jsx +0 -190
  877. package/src/components/highlighting/ui/highlight-tooltip.jsx +0 -143
  878. package/src/components/highlighting/ui/highlighting-ui.jsx +0 -163
  879. package/src/components/highlighting/ui/selection-tracker.jsx +0 -184
  880. package/src/components/highlighting/ui/types.js +0 -21
  881. package/src/components/highlighting/ui/util.js +0 -166
  882. package/src/components/highlighting/word-indexer.jsx +0 -155
  883. package/src/components/hud.jsx +0 -179
  884. package/src/components/icon.jsx +0 -149
  885. package/src/components/image-loader.jsx +0 -175
  886. package/src/components/info-tip/info-tip.jsx +0 -146
  887. package/src/components/info-tip.jsx +0 -38
  888. package/src/components/inline-icon.jsx +0 -81
  889. package/src/components/input-with-examples.jsx +0 -248
  890. package/src/components/lint.jsx +0 -455
  891. package/src/components/math-input.jsx +0 -293
  892. package/src/components/math-output.jsx +0 -128
  893. package/src/components/multi-button-group.jsx +0 -137
  894. package/src/components/number-input.jsx +0 -253
  895. package/src/components/prop-check-box.jsx +0 -80
  896. package/src/components/range-input.jsx +0 -61
  897. package/src/components/simple-keypad-input.jsx +0 -88
  898. package/src/components/sortable.jsx +0 -872
  899. package/src/components/stub-tag-editor.jsx +0 -54
  900. package/src/components/svg-image.jsx +0 -765
  901. package/src/components/tex-buttons.jsx +0 -320
  902. package/src/components/tex.jsx +0 -70
  903. package/src/components/text-input.jsx +0 -133
  904. package/src/components/text-list-editor.jsx +0 -157
  905. package/src/components/tooltip.jsx +0 -392
  906. package/src/components/visibility-observer/__stories__/visibility-observer.stories.jsx +0 -140
  907. package/src/components/visibility-observer/get-scroll-parent.js +0 -45
  908. package/src/components/visibility-observer/visibility-observer.js +0 -109
  909. package/src/components/visibility-observer/visibility-observer.jsx +0 -123
  910. package/src/components/zoomable-tex.jsx +0 -70
  911. package/src/components/zoomable.jsx +0 -294
  912. package/src/components.js +0 -17
  913. package/src/definition-context.js +0 -59
  914. package/src/dependencies.js +0 -22
  915. package/src/error-boundary.jsx +0 -48
  916. package/src/extra-widgets.js +0 -75
  917. package/src/hint-renderer.jsx +0 -152
  918. package/src/hints-renderer.jsx +0 -367
  919. package/src/icon-paths.js +0 -120
  920. package/src/index.js +0 -159
  921. package/src/init.js +0 -49
  922. package/src/interaction-tracker.js +0 -60
  923. package/src/interactive2/__tests__/movable-mock.js +0 -48
  924. package/src/interactive2/__tests__/movable-point_test.js +0 -146
  925. package/src/interactive2/interactive-util.js +0 -152
  926. package/src/interactive2/movable-helper-methods.js +0 -160
  927. package/src/interactive2/movable-line-options.js +0 -392
  928. package/src/interactive2/movable-line.js +0 -322
  929. package/src/interactive2/movable-point-options.js +0 -190
  930. package/src/interactive2/movable-point.jsx +0 -597
  931. package/src/interactive2/movable-polygon-options.js +0 -351
  932. package/src/interactive2/movable-polygon.js +0 -368
  933. package/src/interactive2/movable.js +0 -273
  934. package/src/interactive2/objective_.js +0 -79
  935. package/src/interactive2/types.js +0 -3
  936. package/src/interactive2/wrapped-defaults.js +0 -86
  937. package/src/interactive2/wrapped-ellipse.js +0 -86
  938. package/src/interactive2/wrapped-line.js +0 -109
  939. package/src/interactive2/wrapped-path.js +0 -47
  940. package/src/interactive2.js +0 -91
  941. package/src/item-renderer.jsx +0 -514
  942. package/src/item-version.js +0 -12
  943. package/src/jipt-paragraphs.js +0 -52
  944. package/src/keypad-context.js +0 -33
  945. package/src/loading-context.js +0 -20
  946. package/src/logging/log.js +0 -107
  947. package/src/logging/log_test.js +0 -41
  948. package/src/mixins/__tests__/widget-prop-denylist_test.js +0 -23
  949. package/src/mixins/changeable.jsx +0 -115
  950. package/src/mixins/editor-jsonify.jsx +0 -13
  951. package/src/mixins/provide-keypad.jsx +0 -131
  952. package/src/mixins/widget-jsonify-deprecated.jsx +0 -23
  953. package/src/mixins/widget-prop-denylist.js +0 -49
  954. package/src/multi-items/__stories__/multi-renderer.stories.js +0 -88
  955. package/src/multi-items/__testdata__/multi-renderer_testdata.js +0 -174
  956. package/src/multi-items/__tests__/items_test.js +0 -245
  957. package/src/multi-items/__tests__/multi-renderer_test.js +0 -903
  958. package/src/multi-items/__tests__/prop-type-builders_test.js +0 -134
  959. package/src/multi-items/__tests__/shapes_test.js +0 -44
  960. package/src/multi-items/__tests__/trees_test.js +0 -316
  961. package/src/multi-items/items.js +0 -176
  962. package/src/multi-items/multi-renderer.jsx +0 -543
  963. package/src/multi-items/prop-type-builders.js +0 -105
  964. package/src/multi-items/shape-types.js +0 -47
  965. package/src/multi-items/shapes.js +0 -53
  966. package/src/multi-items/tree-types.js +0 -42
  967. package/src/multi-items/trees.js +0 -345
  968. package/src/multi-items.js +0 -76
  969. package/src/perseus-api.jsx +0 -214
  970. package/src/perseus-env.js +0 -26
  971. package/src/perseus-error.js +0 -19
  972. package/src/perseus-markdown.jsx +0 -408
  973. package/src/perseus-types.js +0 -1471
  974. package/src/question-paragraph.jsx +0 -30
  975. package/src/renderability.jsx +0 -108
  976. package/src/renderer.jsx +0 -1937
  977. package/src/server-item-renderer.jsx +0 -467
  978. package/src/sigfigs.jsx +0 -492
  979. package/src/styles/constants.js +0 -96
  980. package/src/styles/global-constants.js +0 -344
  981. package/src/styles/global-styles.js +0 -307
  982. package/src/styles/media-queries.js +0 -48
  983. package/src/styles/shared.js +0 -132
  984. package/src/tex-wrangler.js +0 -176
  985. package/src/translation-linter.js +0 -125
  986. package/src/traversal.jsx +0 -141
  987. package/src/types.js +0 -482
  988. package/src/util/a11y.js +0 -24
  989. package/src/util/answer-types.js +0 -861
  990. package/src/util/answer-types_test.js +0 -115
  991. package/src/util/colors.js +0 -94
  992. package/src/util/fix-passage-refs.jsx +0 -122
  993. package/src/util/graph-utils.js +0 -16
  994. package/src/util/graphie.js +0 -1586
  995. package/src/util/interactive.js +0 -4078
  996. package/src/util/katex-preprocess.js +0 -16
  997. package/src/util/math.js +0 -289
  998. package/src/util/mobile-native-utils.js +0 -14
  999. package/src/util/mobile-native-utils_test.js +0 -24
  1000. package/src/util/react-render.js +0 -27
  1001. package/src/util/register-all-widgets-for-testing.js +0 -12
  1002. package/src/util/scroll-utils.js +0 -162
  1003. package/src/util/sizing-utils.js +0 -46
  1004. package/src/util/tex.js +0 -203
  1005. package/src/util/url-utils.js +0 -36
  1006. package/src/util.js +0 -984
  1007. package/src/version.js +0 -11
  1008. package/src/widget-container.jsx +0 -178
  1009. package/src/widgets/__stories__/categorizer.stories.jsx +0 -15
  1010. package/src/widgets/__stories__/cs-program.stories.jsx +0 -15
  1011. package/src/widgets/__stories__/definition.stories.jsx +0 -108
  1012. package/src/widgets/__stories__/dropdown.stories.jsx +0 -15
  1013. package/src/widgets/__stories__/explanation.stories.jsx +0 -19
  1014. package/src/widgets/__stories__/expression.stories.jsx +0 -26
  1015. package/src/widgets/__stories__/graded-group-set.stories.jsx +0 -32
  1016. package/src/widgets/__stories__/graded-group.stories.jsx +0 -32
  1017. package/src/widgets/__stories__/grapher.stories.jsx +0 -47
  1018. package/src/widgets/__stories__/group.stories.jsx +0 -15
  1019. package/src/widgets/__stories__/iframe.stories.jsx +0 -15
  1020. package/src/widgets/__stories__/image.stories.jsx +0 -62
  1021. package/src/widgets/__stories__/input-number.stories.jsx +0 -102
  1022. package/src/widgets/__stories__/interaction.stories.jsx +0 -15
  1023. package/src/widgets/__stories__/interactive-graph.stories.jsx +0 -61
  1024. package/src/widgets/__stories__/matcher.stories.jsx +0 -15
  1025. package/src/widgets/__stories__/matrix.stories.jsx +0 -15
  1026. package/src/widgets/__stories__/number-line.stories.jsx +0 -15
  1027. package/src/widgets/__stories__/numeric-input.stories.jsx +0 -106
  1028. package/src/widgets/__stories__/on-screen-keypad.stories.jsx +0 -118
  1029. package/src/widgets/__stories__/orderer.stories.jsx +0 -15
  1030. package/src/widgets/__stories__/passage-ref.stories.jsx +0 -23
  1031. package/src/widgets/__stories__/passage.stories.jsx +0 -27
  1032. package/src/widgets/__stories__/radio.stories.jsx +0 -90
  1033. package/src/widgets/__stories__/sorter.stories.jsx +0 -15
  1034. package/src/widgets/__stories__/transformer.stories.jsx +0 -15
  1035. package/src/widgets/__stories__/unit.stories.jsx +0 -29
  1036. package/src/widgets/__stories__/video-transcript-link.stories.jsx +0 -24
  1037. package/src/widgets/__stories__/video.stories.jsx +0 -15
  1038. package/src/widgets/__testdata__/base-radio_testdata.js +0 -22
  1039. package/src/widgets/__testdata__/categorizer_testdata.js +0 -50
  1040. package/src/widgets/__testdata__/cs-program_testdata.js +0 -28
  1041. package/src/widgets/__testdata__/dropdown_testdata.js +0 -34
  1042. package/src/widgets/__testdata__/explanation_testdata.js +0 -56
  1043. package/src/widgets/__testdata__/expression_testdata.js +0 -95
  1044. package/src/widgets/__testdata__/graded-group-set_testdata.js +0 -358
  1045. package/src/widgets/__testdata__/graded-group_testdata.js +0 -79
  1046. package/src/widgets/__testdata__/grapher_testdata.js +0 -376
  1047. package/src/widgets/__testdata__/group_testdata.js +0 -156
  1048. package/src/widgets/__testdata__/iframe_testdata.js +0 -36
  1049. package/src/widgets/__testdata__/image_testdata.js +0 -38
  1050. package/src/widgets/__testdata__/input-number_testdata.js +0 -73
  1051. package/src/widgets/__testdata__/interaction_testdata.js +0 -264
  1052. package/src/widgets/__testdata__/interactive-graph_testdata.js +0 -646
  1053. package/src/widgets/__testdata__/matcher_testdata.js +0 -34
  1054. package/src/widgets/__testdata__/matrix_testdata.js +0 -29
  1055. package/src/widgets/__testdata__/number-line_testdata.js +0 -31
  1056. package/src/widgets/__testdata__/numeric-input_testdata.js +0 -266
  1057. package/src/widgets/__testdata__/orderer_testdata.js +0 -58
  1058. package/src/widgets/__testdata__/passage-ref_testdata.js +0 -103
  1059. package/src/widgets/__testdata__/passage_testdata.js +0 -96
  1060. package/src/widgets/__testdata__/radio_testdata.js +0 -262
  1061. package/src/widgets/__testdata__/sorter_testdata.js +0 -20
  1062. package/src/widgets/__testdata__/transformer_testdata.js +0 -109
  1063. package/src/widgets/__testdata__/video_testdata.js +0 -22
  1064. package/src/widgets/__tests__/categorizer_test.jsx +0 -205
  1065. package/src/widgets/__tests__/cs-program_test.jsx +0 -45
  1066. package/src/widgets/__tests__/definition_test.js +0 -137
  1067. package/src/widgets/__tests__/dropdown_test.jsx +0 -106
  1068. package/src/widgets/__tests__/explanation_test.js +0 -199
  1069. package/src/widgets/__tests__/expression_test.jsx +0 -429
  1070. package/src/widgets/__tests__/graded-group-set-jipt_test.jsx +0 -32
  1071. package/src/widgets/__tests__/graded-group-set_test.jsx +0 -290
  1072. package/src/widgets/__tests__/graded-group_test.jsx +0 -233
  1073. package/src/widgets/__tests__/grapher.cypress.jsx +0 -546
  1074. package/src/widgets/__tests__/grapher_test.jsx +0 -35
  1075. package/src/widgets/__tests__/group_test.jsx +0 -512
  1076. package/src/widgets/__tests__/iframe_test.jsx +0 -47
  1077. package/src/widgets/__tests__/image_test.jsx +0 -65
  1078. package/src/widgets/__tests__/input-number_test.jsx +0 -352
  1079. package/src/widgets/__tests__/interaction_test.jsx +0 -33
  1080. package/src/widgets/__tests__/interactive-graph.cypress.jsx +0 -777
  1081. package/src/widgets/__tests__/interactive-graph_test.jsx +0 -105
  1082. package/src/widgets/__tests__/matcher_test.jsx +0 -157
  1083. package/src/widgets/__tests__/matrix_test.jsx +0 -84
  1084. package/src/widgets/__tests__/number-line_test.jsx +0 -81
  1085. package/src/widgets/__tests__/numeric-input_test.jsx +0 -503
  1086. package/src/widgets/__tests__/orderer_test.jsx +0 -75
  1087. package/src/widgets/__tests__/passage-ref_test.jsx +0 -166
  1088. package/src/widgets/__tests__/passage_test.jsx +0 -212
  1089. package/src/widgets/__tests__/radio/base-radio_test.jsx +0 -443
  1090. package/src/widgets/__tests__/radio/choice-icon_test.jsx +0 -91
  1091. package/src/widgets/__tests__/radio/choice_test.jsx +0 -311
  1092. package/src/widgets/__tests__/radio/focus-ring_test.jsx +0 -23
  1093. package/src/widgets/__tests__/radio/option-status_test.jsx +0 -65
  1094. package/src/widgets/__tests__/radio_test.jsx +0 -810
  1095. package/src/widgets/__tests__/renderQuestion.jsx +0 -66
  1096. package/src/widgets/__tests__/unit_test.jsx +0 -169
  1097. package/src/widgets/__tests__/video_test.jsx +0 -55
  1098. package/src/widgets/categorizer.jsx +0 -343
  1099. package/src/widgets/cs-program.jsx +0 -227
  1100. package/src/widgets/definition.jsx +0 -121
  1101. package/src/widgets/dropdown.jsx +0 -141
  1102. package/src/widgets/explanation.jsx +0 -315
  1103. package/src/widgets/expression.jsx +0 -629
  1104. package/src/widgets/graded-group-answer-bar.jsx +0 -152
  1105. package/src/widgets/graded-group-set.jsx +0 -296
  1106. package/src/widgets/graded-group.jsx +0 -453
  1107. package/src/widgets/grapher/util.jsx +0 -806
  1108. package/src/widgets/grapher.jsx +0 -663
  1109. package/src/widgets/group.jsx +0 -195
  1110. package/src/widgets/iframe.jsx +0 -198
  1111. package/src/widgets/image.jsx +0 -310
  1112. package/src/widgets/input-number.jsx +0 -402
  1113. package/src/widgets/interaction/arrow-picker.jsx +0 -39
  1114. package/src/widgets/interaction/color-picker.jsx +0 -69
  1115. package/src/widgets/interaction/dash-picker.jsx +0 -46
  1116. package/src/widgets/interaction/element-container.jsx +0 -97
  1117. package/src/widgets/interaction.jsx +0 -742
  1118. package/src/widgets/interactive-graph.jsx +0 -2820
  1119. package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +0 -93
  1120. package/src/widgets/label-image/__stories__/marker.stories.jsx +0 -172
  1121. package/src/widgets/label-image/answer-choices.jsx +0 -311
  1122. package/src/widgets/label-image/marker.jsx +0 -365
  1123. package/src/widgets/label-image/types.js +0 -33
  1124. package/src/widgets/label-image.jsx +0 -841
  1125. package/src/widgets/label-image_test.js +0 -840
  1126. package/src/widgets/lights-puzzle.jsx +0 -356
  1127. package/src/widgets/matcher.jsx +0 -274
  1128. package/src/widgets/matrix.jsx +0 -616
  1129. package/src/widgets/measurer.jsx +0 -215
  1130. package/src/widgets/molecule/__tests__/layout_test.jsx +0 -159
  1131. package/src/widgets/molecule/__tests__/parser_test.jsx +0 -142
  1132. package/src/widgets/molecule/molecule-drawing.jsx +0 -175
  1133. package/src/widgets/molecule/molecule-layout.jsx +0 -367
  1134. package/src/widgets/molecule/smiles-parser.jsx +0 -300
  1135. package/src/widgets/molecule.jsx +0 -172
  1136. package/src/widgets/number-line.jsx +0 -855
  1137. package/src/widgets/numeric-input.jsx +0 -546
  1138. package/src/widgets/orderer.jsx +0 -756
  1139. package/src/widgets/passage/__tests__/passage-markdown_test.jsx +0 -373
  1140. package/src/widgets/passage/passage-markdown.jsx +0 -472
  1141. package/src/widgets/passage-ref-target.jsx +0 -73
  1142. package/src/widgets/passage-ref.jsx +0 -214
  1143. package/src/widgets/passage.jsx +0 -576
  1144. package/src/widgets/plotter.jsx +0 -1198
  1145. package/src/widgets/radio/__stories__/base-radio.stories.jsx +0 -189
  1146. package/src/widgets/radio/__stories__/choice-icon.stories.jsx +0 -166
  1147. package/src/widgets/radio/__stories__/choice-none-above.stories.jsx +0 -41
  1148. package/src/widgets/radio/__stories__/choice.stories.jsx +0 -138
  1149. package/src/widgets/radio/__stories__/focus-ring.stories.jsx +0 -44
  1150. package/src/widgets/radio/__stories__/option-status.stories.jsx +0 -104
  1151. package/src/widgets/radio/base-radio.jsx +0 -494
  1152. package/src/widgets/radio/choice-a11y-text.js +0 -62
  1153. package/src/widgets/radio/choice-icon/choice-icon.jsx +0 -214
  1154. package/src/widgets/radio/choice-icon/cross-out-line.jsx +0 -52
  1155. package/src/widgets/radio/choice-icon/shared-styles.js +0 -16
  1156. package/src/widgets/radio/choice-none-above.jsx +0 -63
  1157. package/src/widgets/radio/choice.jsx +0 -373
  1158. package/src/widgets/radio/choice.jsx.fixture.js +0 -46
  1159. package/src/widgets/radio/focus-ring.jsx +0 -58
  1160. package/src/widgets/radio/option-status.jsx +0 -98
  1161. package/src/widgets/radio/util.js +0 -32
  1162. package/src/widgets/radio/util_test.js +0 -19
  1163. package/src/widgets/radio/widget.jsx +0 -515
  1164. package/src/widgets/radio.jsx +0 -155
  1165. package/src/widgets/reaction-diagram.jsx +0 -137
  1166. package/src/widgets/sequence.jsx +0 -143
  1167. package/src/widgets/simulator.jsx +0 -802
  1168. package/src/widgets/sorter.jsx +0 -149
  1169. package/src/widgets/table.jsx +0 -389
  1170. package/src/widgets/transformer.jsx +0 -2872
  1171. package/src/widgets/unit.jsx +0 -357
  1172. package/src/widgets/video-transcript-link.jsx +0 -84
  1173. package/src/widgets/video-transcript-link_test.js +0 -211
  1174. package/src/widgets/video.jsx +0 -126
  1175. package/src/widgets.js +0 -431
  1176. package/src/zoom.js +0 -539
  1177. /package/{src/multi-items/item-types.js → dist/multi-items/item-types.js.flow} +0 -0
  1178. /package/dist/{strings.js → strings} +0 -0
  1179. /package/src/__tests__/__snapshots__/{item-renderer_test.jsx.snap → item-renderer.test.tsx.snap} +0 -0
  1180. /package/src/__tests__/__snapshots__/{perseus-markdown_test.jsx.snap → perseus-markdown.test.ts.snap} +0 -0
  1181. /package/src/__tests__/__snapshots__/{renderer_test.jsx.snap → renderer.test.tsx.snap} +0 -0
  1182. /package/src/__tests__/__snapshots__/{server-item-renderer_test.jsx.snap → server-item-renderer.test.tsx.snap} +0 -0
  1183. /package/src/components/__tests__/__snapshots__/{sortable_test.jsx.snap → sortable.test.tsx.snap} +0 -0
  1184. /package/src/components/__tests__/__snapshots__/{sorter_test.jsx.snap → sorter.test.tsx.snap} +0 -0
  1185. /package/src/multi-items/__tests__/__snapshots__/{multi-renderer_test.js.snap → multi-renderer.test.tsx.snap} +0 -0
  1186. /package/src/widgets/__tests__/__snapshots__/{categorizer_test.jsx.snap → categorizer.test.ts.snap} +0 -0
  1187. /package/src/widgets/__tests__/__snapshots__/{cs-program_test.jsx.snap → cs-program.test.ts.snap} +0 -0
  1188. /package/src/widgets/__tests__/__snapshots__/{definition_test.js.snap → definition.test.ts.snap} +0 -0
  1189. /package/src/widgets/__tests__/__snapshots__/{dropdown_test.jsx.snap → dropdown.test.ts.snap} +0 -0
  1190. /package/src/widgets/__tests__/__snapshots__/{explanation_test.js.snap → explanation.test.ts.snap} +0 -0
  1191. /package/src/widgets/__tests__/__snapshots__/{graded-group-set-jipt_test.jsx.snap → graded-group-set-jipt.test.ts.snap} +0 -0
  1192. /package/src/widgets/__tests__/__snapshots__/{graded-group-set_test.jsx.snap → graded-group-set.test.ts.snap} +0 -0
  1193. /package/src/widgets/__tests__/__snapshots__/{graded-group_test.jsx.snap → graded-group.test.ts.snap} +0 -0
  1194. /package/src/widgets/__tests__/__snapshots__/{grapher_test.jsx.snap → grapher.test.ts.snap} +0 -0
  1195. /package/src/widgets/__tests__/__snapshots__/{group_test.jsx.snap → group.test.tsx.snap} +0 -0
  1196. /package/src/widgets/__tests__/__snapshots__/{iframe_test.jsx.snap → iframe.test.ts.snap} +0 -0
  1197. /package/src/widgets/__tests__/__snapshots__/{image_test.jsx.snap → image.test.ts.snap} +0 -0
  1198. /package/src/widgets/__tests__/__snapshots__/{input-number_test.jsx.snap → input-number.test.ts.snap} +0 -0
  1199. /package/src/widgets/__tests__/__snapshots__/{interaction_test.jsx.snap → interaction.test.ts.snap} +0 -0
  1200. /package/src/widgets/__tests__/__snapshots__/{interactive-graph_test.jsx.snap → interactive-graph.test.ts.snap} +0 -0
  1201. /package/src/widgets/__tests__/__snapshots__/{matcher_test.jsx.snap → matcher.test.tsx.snap} +0 -0
  1202. /package/src/widgets/__tests__/__snapshots__/{matrix_test.jsx.snap → matrix.test.ts.snap} +0 -0
  1203. /package/src/widgets/__tests__/__snapshots__/{number-line_test.jsx.snap → number-line.test.ts.snap} +0 -0
  1204. /package/src/widgets/__tests__/__snapshots__/{numeric-input_test.jsx.snap → numeric-input.test.ts.snap} +0 -0
  1205. /package/src/widgets/__tests__/__snapshots__/{orderer_test.jsx.snap → orderer.test.ts.snap} +0 -0
  1206. /package/src/widgets/__tests__/__snapshots__/{passage-ref_test.jsx.snap → passage-ref.test.ts.snap} +0 -0
  1207. /package/src/widgets/__tests__/__snapshots__/{passage_test.jsx.snap → passage.test.tsx.snap} +0 -0
  1208. /package/src/widgets/__tests__/__snapshots__/{radio_test.jsx.snap → radio.test.ts.snap} +0 -0
  1209. /package/src/widgets/__tests__/__snapshots__/{unit_test.jsx.snap → unit.test.tsx.snap} +0 -0
  1210. /package/src/widgets/__tests__/__snapshots__/{video_test.jsx.snap → video.test.ts.snap} +0 -0
@@ -0,0 +1,4191 @@
1
+ /* eslint-disable @babel/no-invalid-this */
2
+ /**
3
+ * Interactive graphie utilities.
4
+ *
5
+ * This file exposes a couple functions, but mostly it adds functions to the
6
+ * `Graphie` prototype for dealing with interactive graphie elements.
7
+ */
8
+
9
+ // TODO(emily): This file breaks our line length limits like nobody's business.
10
+ // Figure out how to fix that.
11
+ import {
12
+ vector as kvector,
13
+ point as kpoint,
14
+ line as kline,
15
+ } from "@khanacademy/kmath";
16
+ import $ from "jquery";
17
+ // eslint-disable-next-line import/no-extraneous-dependencies
18
+ import Raphael from "raphael";
19
+ import _ from "underscore";
20
+
21
+ // NOTE(jeresig): We include a special copy of jQuery vmouse so
22
+ // that Perseus will continue to work in mobile native devices
23
+ // (this should have no impact in the browser)
24
+ // eslint-disable-next-line import/no-unassigned-import
25
+ import "../jquery.mobile.vmouse";
26
+ import InteractiveUtil from "../interactive2/interactive-util";
27
+ import WrappedEllipse from "../interactive2/wrapped-ellipse";
28
+ import WrappedLine from "../interactive2/wrapped-line";
29
+ import WrappedPath from "../interactive2/wrapped-path";
30
+ import {Errors} from "../logging/log";
31
+ import {PerseusError} from "../perseus-error";
32
+
33
+ import KhanColors from "./colors";
34
+ import GraphUtils from "./graphie";
35
+ import KhanMath from "./math";
36
+
37
+ import type {Coord} from "../interactive2/types";
38
+
39
+ const {getCanUse3dTransform} = InteractiveUtil;
40
+
41
+ function sum(array: any) {
42
+ return _.reduce(
43
+ array,
44
+ function (memo, arg) {
45
+ return memo + arg;
46
+ },
47
+ 0,
48
+ );
49
+ }
50
+
51
+ function clockwise(points: any) {
52
+ const segments = _.zip(points, points.slice(1).concat(points.slice(0, 1)));
53
+ const areas = _.map(segments, function (segment) {
54
+ const p1 = segment[0];
55
+ const p2 = segment[1];
56
+ return (p2[0] - p1[0]) * (p2[1] + p1[1]);
57
+ });
58
+ return sum(areas) > 0;
59
+ }
60
+
61
+ /* vector-add multiple [x, y] coords/vectors */
62
+ function addPoints(...points) {
63
+ const zipped = _.zip(...points);
64
+ return _.map(zipped, sum);
65
+ }
66
+
67
+ function reverseVector(vector: Array<number>) {
68
+ return _.map(vector, function (coord) {
69
+ return coord * -1;
70
+ });
71
+ }
72
+
73
+ function scaledDistanceFromAngle(angle: number) {
74
+ const a = 3.51470560176242 * 20;
75
+ const b = 0.5687298702748785 * 20;
76
+ const c = -0.037587715462826674;
77
+ return (a - b) * Math.exp(c * angle) + b;
78
+ }
79
+
80
+ function scaledPolarRad(radius: number, radians: number) {
81
+ return [
82
+ radius * Math.cos(radians),
83
+ radius * Math.sin(radians) * -1, // SVG flips y axis
84
+ ];
85
+ }
86
+
87
+ function scaledPolarDeg(radius: number, degrees) {
88
+ const radians = (degrees * Math.PI) / 180;
89
+ return scaledPolarRad(radius, radians);
90
+ }
91
+
92
+ // Global dragging state
93
+ let dragging = false;
94
+
95
+ const InteractiveUtils: any = {
96
+ // Useful for shapes that are only sometimes drawn. If a shape isn't
97
+ // needed, it can be replaced with bogusShape which just has stub methods
98
+ // that successfully do nothing.
99
+ // The alternative would be 'if..typeof' checks all over the place.
100
+ bogusShape: {
101
+ animate: function () {},
102
+ attr: function () {},
103
+ remove: function () {},
104
+ },
105
+ };
106
+
107
+ _.extend(GraphUtils.Graphie.prototype, {
108
+ // graphie puts text spans on top of the SVG, which looks good, but gets
109
+ // in the way of mouse events. This adds another SVG element on top
110
+ // of everything else where we can add invisible shapes with mouse
111
+ // handlers wherever we want.
112
+ addMouseLayer: function (options) {
113
+ const graph = this;
114
+ options = _.extend(
115
+ {
116
+ allowScratchpad: false,
117
+ setDrawingAreaAvailable: function () {},
118
+ },
119
+ options,
120
+ );
121
+
122
+ const mouselayerZIndex = 2;
123
+ graph.mouselayer = Raphael(
124
+ graph.raphael.canvas.parentNode,
125
+ graph.xpixels,
126
+ graph.ypixels,
127
+ );
128
+ $(graph.mouselayer.canvas).css("z-index", mouselayerZIndex);
129
+ if (
130
+ options.onClick ||
131
+ options.onMouseDown ||
132
+ options.onMouseMove ||
133
+ options.onMouseOver ||
134
+ options.onMouseOut
135
+ ) {
136
+ const canvasClickTarget = graph.mouselayer
137
+ .rect(0, 0, graph.xpixels, graph.ypixels)
138
+ .attr({
139
+ fill: "#000",
140
+ opacity: 0,
141
+ });
142
+ let isClickingCanvas = false;
143
+
144
+ $(graph.mouselayer.canvas).on("vmousedown", function (e) {
145
+ if (e.target === canvasClickTarget[0]) {
146
+ if (options.onMouseDown) {
147
+ options.onMouseDown(graph.getMouseCoord(e));
148
+ }
149
+ isClickingCanvas = true;
150
+
151
+ if (options.onMouseMove) {
152
+ $(document).bind("vmousemove.mouseLayer", function (e) {
153
+ if (isClickingCanvas) {
154
+ e.preventDefault();
155
+ options.onMouseMove(graph.getMouseCoord(e));
156
+ }
157
+ });
158
+ }
159
+
160
+ $(document).bind("vmouseup.mouseLayer", function (e) {
161
+ $(document).unbind(".mouseLayer");
162
+
163
+ // Only register clicks that started on the canvas,
164
+ // and not on another mouseLayer target
165
+ if (isClickingCanvas && options.onClick) {
166
+ options.onClick(graph.getMouseCoord(e));
167
+ }
168
+ isClickingCanvas = false;
169
+ });
170
+ }
171
+ });
172
+ if (options.onMouseOver) {
173
+ $(graph.mouselayer.canvas).on("vmouseover", function (e) {
174
+ options.onMouseOver(graph.getMouseCoord(e));
175
+ });
176
+ }
177
+ if (options.onMouseOut) {
178
+ $(graph.mouselayer.canvas).on("vmouseout", function (e) {
179
+ options.onMouseOut(graph.getMouseCoord(e));
180
+ });
181
+ }
182
+ }
183
+ if (!options.allowScratchpad) {
184
+ options.setDrawingAreaAvailable(false);
185
+ }
186
+
187
+ // Add mouse and visible wrapper layers for DOM-node-wrapped movables
188
+ graph._mouselayerWrapper = document.createElement("div");
189
+ $(graph._mouselayerWrapper).css({
190
+ position: "absolute",
191
+ left: 0,
192
+ top: 0,
193
+ zIndex: mouselayerZIndex,
194
+ });
195
+
196
+ graph._visiblelayerWrapper = document.createElement("div");
197
+ $(graph._visiblelayerWrapper).css({
198
+ position: "absolute",
199
+ left: 0,
200
+ top: 0,
201
+ });
202
+
203
+ const el = graph.raphael.canvas.parentNode;
204
+ el.appendChild(graph._visiblelayerWrapper);
205
+ el.appendChild(graph._mouselayerWrapper);
206
+
207
+ // Add functions for adding to wrappers
208
+ graph.addToMouseLayerWrapper = function (el: any) {
209
+ this._mouselayerWrapper.appendChild(el);
210
+ };
211
+ graph.addToVisibleLayerWrapper = function (el: any) {
212
+ this._visiblelayerWrapper.appendChild(el);
213
+ };
214
+ },
215
+
216
+ /**
217
+ * Get mouse coordinates in pixels
218
+ */
219
+ getMousePx: function (event) {
220
+ const graphie = this;
221
+
222
+ const mouseX =
223
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
224
+ event.pageX - $(graphie.raphael.canvas.parentNode).offset().left;
225
+ const mouseY =
226
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
227
+ event.pageY - $(graphie.raphael.canvas.parentNode).offset().top;
228
+
229
+ return [mouseX, mouseY];
230
+ },
231
+
232
+ /**
233
+ * Get mouse coordinates in graph coordinates
234
+ */
235
+ getMouseCoord: function (event) {
236
+ return this.unscalePoint(this.getMousePx(event));
237
+ },
238
+
239
+ /**
240
+ * Unlike all other Graphie-related code, the following three functions use
241
+ * a lot of scaled coordinates (so that labels appear the same size
242
+ * regardless of current shape/figure scale). These are prefixed with 's'.
243
+ */
244
+ labelAngle: function (options) {
245
+ const graphie = this;
246
+
247
+ _.defaults(options, {
248
+ point1: [0, 0],
249
+ vertex: [0, 0],
250
+ point3: [0, 0],
251
+ label: null,
252
+ numArcs: 1,
253
+ showRightAngleMarker: true,
254
+ pushOut: 0,
255
+ clockwise: false,
256
+ style: {},
257
+ });
258
+
259
+ let text = options.text === undefined ? "" : options.text;
260
+ const vertex = options.vertex;
261
+ const sVertex = graphie.scalePoint(vertex);
262
+ let p1;
263
+ let p3;
264
+ if (options.clockwise) {
265
+ p1 = options.point1;
266
+ p3 = options.point3;
267
+ } else {
268
+ p1 = options.point3;
269
+ p3 = options.point1;
270
+ }
271
+
272
+ const startAngle = GraphUtils.findAngle(p1, vertex);
273
+ const endAngle = GraphUtils.findAngle(p3, vertex);
274
+ const angle = (endAngle + 360 - startAngle) % 360;
275
+ const halfAngle = (startAngle + angle / 2) % 360;
276
+ const sPadding = 5 * options.pushOut;
277
+ let sRadius = sPadding + scaledDistanceFromAngle(angle);
278
+ const temp: Array<never> = [];
279
+
280
+ if (Math.abs(angle - 90) < 1e-9 && options.showRightAngleMarker) {
281
+ const v1 = addPoints(sVertex, scaledPolarDeg(sRadius, startAngle));
282
+ const v2 = addPoints(sVertex, scaledPolarDeg(sRadius, endAngle));
283
+
284
+ sRadius *= Math.SQRT2;
285
+ const v3 = addPoints(sVertex, scaledPolarDeg(sRadius, halfAngle));
286
+
287
+ _.each([v1, v2], function (v) {
288
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
289
+ temp.push(graphie.scaledPath([v, v3], options.style));
290
+ });
291
+ } else {
292
+ // Draw arcs
293
+ _.times(options.numArcs, function (i) {
294
+ temp.push(
295
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
296
+ graphie.arc(
297
+ vertex,
298
+ graphie.unscaleVector(sRadius),
299
+ startAngle,
300
+ endAngle,
301
+ options.style,
302
+ ),
303
+ );
304
+ sRadius += 3;
305
+ });
306
+ }
307
+
308
+ if (text) {
309
+ const match = text.match(/\$deg(\d)?/);
310
+ if (match) {
311
+ const precision = match[1] || 1;
312
+ text = text.replace(
313
+ match[0],
314
+ KhanMath.toFixedApprox(angle, precision) + "^{\\circ}",
315
+ );
316
+ }
317
+
318
+ const sOffset = scaledPolarDeg(sRadius + 15, halfAngle);
319
+ const sPosition = addPoints(sVertex, sOffset);
320
+ const position = graphie.unscalePoint(sPosition);
321
+
322
+ // Reuse label if possible
323
+ if (options.label) {
324
+ options.label.setPosition(position);
325
+ options.label.processMath(text, /* force */ true);
326
+ } else {
327
+ graphie.label(position, text, "center", options.style);
328
+ }
329
+ }
330
+
331
+ return temp;
332
+ },
333
+
334
+ labelSide: function (options) {
335
+ const graphie = this;
336
+
337
+ _.defaults(options, {
338
+ point1: [0, 0],
339
+ point2: [0, 0],
340
+ label: null,
341
+ text: "",
342
+ numTicks: 0,
343
+ numArrows: 0,
344
+ clockwise: false,
345
+ style: {},
346
+ });
347
+
348
+ let p1;
349
+ let p2;
350
+ if (options.clockwise) {
351
+ p1 = options.point1;
352
+ p2 = options.point2;
353
+ } else {
354
+ p1 = options.point2;
355
+ p2 = options.point1;
356
+ }
357
+
358
+ const midpoint = [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2];
359
+ const sMidpoint = graphie.scalePoint(midpoint);
360
+ const parallelAngle = Math.atan2(p2[1] - p1[1], p2[0] - p1[0]);
361
+ const perpendicularAngle = parallelAngle + Math.PI / 2;
362
+ const temp: Array<never> = [];
363
+ let sCumulativeOffset = 0;
364
+
365
+ if (options.numTicks) {
366
+ const n = options.numTicks;
367
+
368
+ const sSpacing = 5;
369
+ const sHeight = 5;
370
+
371
+ const style = _.extend({}, options.style, {
372
+ strokeWidth: 2,
373
+ });
374
+
375
+ _.times(n, function (i) {
376
+ const sOffset = sSpacing * (i - (n - 1) / 2);
377
+
378
+ const sOffsetVector = scaledPolarRad(sOffset, parallelAngle);
379
+ const sHeightVector = scaledPolarRad(
380
+ sHeight,
381
+ perpendicularAngle,
382
+ );
383
+
384
+ const sPath = [
385
+ addPoints(sMidpoint, sOffsetVector, sHeightVector),
386
+ addPoints(
387
+ sMidpoint,
388
+ sOffsetVector,
389
+ reverseVector(sHeightVector),
390
+ ),
391
+ ];
392
+
393
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
394
+ temp.push(graphie.scaledPath(sPath, style));
395
+ });
396
+
397
+ sCumulativeOffset += sSpacing * (n - 1) + 15;
398
+ }
399
+
400
+ if (options.numArrows) {
401
+ const n = options.numArrows;
402
+
403
+ const start = [p1, p2].sort(function (a, b) {
404
+ if (a[1] === b[1]) {
405
+ return a[0] - b[0];
406
+ }
407
+ return a[1] - b[1];
408
+ })[0];
409
+ const sStart = graphie.scalePoint(start);
410
+
411
+ const style = _.extend({}, options.style, {
412
+ arrows: "->",
413
+ strokeWidth: 2,
414
+ });
415
+
416
+ const sSpacing = 5;
417
+
418
+ _.times(n, function (i) {
419
+ const sOffset = sCumulativeOffset + sSpacing * i;
420
+ let sOffsetVector = scaledPolarRad(sOffset, parallelAngle);
421
+
422
+ if (start !== p1) {
423
+ sOffsetVector = reverseVector(sOffsetVector);
424
+ }
425
+
426
+ const sEnd = addPoints(sMidpoint, sOffsetVector);
427
+
428
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
429
+ temp.push(graphie.scaledPath([sStart, sEnd], style));
430
+ });
431
+ }
432
+
433
+ let text = options.text;
434
+ if (text) {
435
+ const match = text.match(/\$len(\d)?/);
436
+ if (match) {
437
+ const distance = GraphUtils.getDistance(p1, p2);
438
+ const precision = match[1] || 1;
439
+ text = text.replace(
440
+ match[0],
441
+ KhanMath.toFixedApprox(distance, precision),
442
+ );
443
+ }
444
+
445
+ const sOffset = 20;
446
+ const sOffsetVector = scaledPolarRad(sOffset, perpendicularAngle);
447
+ const sPosition = addPoints(sMidpoint, sOffsetVector);
448
+ const position = graphie.unscalePoint(sPosition);
449
+
450
+ // Reuse label if possible
451
+ if (options.label) {
452
+ options.label.setPosition(position);
453
+ options.label.processMath(text, /* force */ true);
454
+ } else {
455
+ graphie.label(position, text, "center", options.style);
456
+ }
457
+ }
458
+
459
+ return temp;
460
+ },
461
+
462
+ /* Can also be used to label points that aren't vertices */
463
+ labelVertex: function (options) {
464
+ const graphie = this;
465
+
466
+ _.defaults(options, {
467
+ point1: null,
468
+ vertex: [0, 0],
469
+ point3: null,
470
+ label: null,
471
+ text: "",
472
+ clockwise: false,
473
+ style: {},
474
+ });
475
+
476
+ if (!options.text) {
477
+ return;
478
+ }
479
+
480
+ const vertex = options.vertex;
481
+ const sVertex = graphie.scalePoint(vertex);
482
+ let p1;
483
+ let p3;
484
+ if (options.clockwise) {
485
+ p1 = options.point1;
486
+ p3 = options.point3;
487
+ } else {
488
+ p1 = options.point3;
489
+ p3 = options.point1;
490
+ }
491
+
492
+ let angle = 135;
493
+ let halfAngle;
494
+ if (p1 && p3) {
495
+ const startAngle = GraphUtils.findAngle(p1, vertex);
496
+ const endAngle = GraphUtils.findAngle(p3, vertex);
497
+ angle = (endAngle + 360 - startAngle) % 360;
498
+ halfAngle = (startAngle + angle / 2 + 180) % 360;
499
+ } else if (p1) {
500
+ const parallelAngle = GraphUtils.findAngle(vertex, p1);
501
+ halfAngle = parallelAngle + 90;
502
+ } else if (p3) {
503
+ const parallelAngle = GraphUtils.findAngle(p3, vertex);
504
+ halfAngle = parallelAngle + 90;
505
+ } else {
506
+ // Standalone point
507
+ halfAngle = 135;
508
+ }
509
+
510
+ const sRadius = 10 + scaledDistanceFromAngle(360 - angle);
511
+ const sOffsetVector = scaledPolarDeg(sRadius, halfAngle);
512
+ const sPosition = addPoints(sVertex, sOffsetVector);
513
+ const position = graphie.unscalePoint(sPosition);
514
+
515
+ // Reuse label if possible
516
+ if (options.label) {
517
+ options.label.setPosition(position);
518
+ options.label.processMath(options.text, /* force */ true);
519
+ } else {
520
+ graphie.label(position, options.text, "center", options.style);
521
+ }
522
+ },
523
+
524
+ // Add a point to the graph that can be dragged around.
525
+ // It allows automatic constraints on its movement as well as automatically
526
+ // managing line segments that terminate at the point.
527
+ //
528
+ // Options can be set to control how the point behaves:
529
+ // coord[]:
530
+ // The initial position of the point
531
+ // snapX, snapY:
532
+ // The minimum increment the point can be moved
533
+ //
534
+ // The return value is an object that can be used to manipulate the point:
535
+ // The coordX and coordY properties tell you the current position
536
+ //
537
+ // By adding an onMove() method to the returned object, you can install an
538
+ // event handler that gets called every time the user moves the point.
539
+ //
540
+ // The returned object also provides a moveTo(x,y) method that will move
541
+ // the point to a specific coordinate
542
+ //
543
+ // Constraints can be set on the on the returned object:
544
+ //
545
+ // - Set point to be immovable:
546
+ // movablePoint.constraints.fixed = true
547
+ //
548
+ // - Constrain point to a fixed distance from another point. The resulting
549
+ // point will move in a circle:
550
+ // movablePoint.fixedDistance = {
551
+ // dist: 2,
552
+ // point: point1
553
+ // }
554
+ //
555
+ // - Constrain point to a line defined by a fixed angle between it and
556
+ // two other points:
557
+ // movablePoint.fixedAngle = {
558
+ // angle: 45,
559
+ // vertex: point1,
560
+ // ref: point2
561
+ // }
562
+ //
563
+ // - Confined the point to traveling in a vertical or horizontal line,
564
+ // respectively
565
+ // movablePoint.constrainX = true;
566
+ // movablePoint.constrainY = true;
567
+ //
568
+ // - Connect a movableLineSegment to a movablePoint. The point is attached
569
+ // to a specific end of the line segment by adding the segment either to
570
+ // the list of lines that start at the point or the list of lines that
571
+ // end at the point (movableLineSegment can do this for you):
572
+ // movablePoint.lineStarts.push(movableLineSegment);
573
+ // - or -
574
+ // movablePoint.lineEnds.push(movableLineSegment);
575
+ //
576
+ // - Connect a movablePolygon to a movablePoint in exacty the same way:
577
+ // movablePoint.polygonVertices.push(movablePolygon);
578
+ //
579
+ addMovablePoint: function (options) {
580
+ const movablePoint = $.extend(
581
+ true,
582
+ {
583
+ graph: this,
584
+ coord: [0, 0],
585
+ snapX: 0,
586
+ snapY: 0,
587
+ pointSize: 4,
588
+ highlight: false,
589
+ dragging: false,
590
+ visible: true,
591
+ bounded: true,
592
+ constraints: {
593
+ fixed: false,
594
+ constrainX: false,
595
+ constrainY: false,
596
+ fixedAngle: {},
597
+ fixedDistance: {},
598
+ },
599
+ lineStarts: [],
600
+ lineEnds: [],
601
+ polygonVertices: [],
602
+ normalStyle: {},
603
+ highlightStyle: {
604
+ fill: KhanColors.INTERACTING,
605
+ stroke: KhanColors.INTERACTING,
606
+ },
607
+ labelStyle: {
608
+ color: KhanColors.INTERACTIVE,
609
+ },
610
+ vertexLabel: "",
611
+ mouseTarget: null,
612
+ },
613
+ options,
614
+ );
615
+
616
+ const normalColor = movablePoint.constraints.fixed
617
+ ? KhanColors.DYNAMIC
618
+ : KhanColors.INTERACTIVE;
619
+ movablePoint.normalStyle = _.extend(
620
+ {},
621
+ {
622
+ fill: normalColor,
623
+ stroke: normalColor,
624
+ },
625
+ options.normalStyle,
626
+ );
627
+
628
+ // deprecated: don't use coordX/coordY; use coord[]
629
+ if (options.coordX !== undefined) {
630
+ movablePoint.coord[0] = options.coordX;
631
+ }
632
+ if (options.coordY !== undefined) {
633
+ movablePoint.coord[1] = options.coordY;
634
+ }
635
+
636
+ const graph = movablePoint.graph;
637
+
638
+ const applySnapAndConstraints = function (coord: any) {
639
+ // coord should be the scaled coordinate
640
+
641
+ // move point away from edge of graph unless it's invisible or fixed
642
+ if (
643
+ movablePoint.visible &&
644
+ movablePoint.bounded &&
645
+ !movablePoint.constraints.fixed
646
+ ) {
647
+ // can't go beyond 10 pixels from the edge
648
+ coord = graph.constrainToBounds(coord, 10);
649
+ }
650
+
651
+ let coordX = coord[0];
652
+ let coordY = coord[1];
653
+
654
+ // snap coordinates to grid
655
+ if (movablePoint.snapX !== 0) {
656
+ coordX =
657
+ Math.round(coordX / movablePoint.snapX) *
658
+ movablePoint.snapX;
659
+ }
660
+ if (movablePoint.snapY !== 0) {
661
+ coordY =
662
+ Math.round(coordY / movablePoint.snapY) *
663
+ movablePoint.snapY;
664
+ }
665
+
666
+ // snap to points around circle
667
+ if (movablePoint.constraints.fixedDistance.snapPoints) {
668
+ const mouse = graph.scalePoint(coord);
669
+ let mouseX = mouse[0];
670
+ let mouseY = mouse[1];
671
+
672
+ const snapRadians =
673
+ (2 * Math.PI) /
674
+ movablePoint.constraints.fixedDistance.snapPoints;
675
+ const radius = movablePoint.constraints.fixedDistance.dist;
676
+
677
+ const centerCoord =
678
+ movablePoint.constraints.fixedDistance.point;
679
+ const centerX =
680
+ (centerCoord[0] - graph.range[0][0]) * graph.scale[0];
681
+ const centerY =
682
+ (-centerCoord[1] + graph.range[1][1]) * graph.scale[1];
683
+
684
+ let mouseXrel = mouseX - centerX;
685
+ let mouseYrel = -mouseY + centerY;
686
+ let radians = Math.atan(mouseYrel / mouseXrel);
687
+ const outsideArcTanRange = mouseXrel < 0;
688
+
689
+ // adjust so that angles increase from 0 to 2 pi as you go
690
+ // around the circle
691
+ if (outsideArcTanRange) {
692
+ radians += Math.PI;
693
+ }
694
+
695
+ // perform the snap
696
+ radians = Math.round(radians / snapRadians) * snapRadians;
697
+
698
+ // convert from radians back to pixels
699
+ mouseXrel = radius * Math.cos(radians);
700
+ mouseYrel = radius * Math.sin(radians);
701
+ // convert back to coordinates relative to graphie canvas
702
+ mouseX = mouseXrel + centerX;
703
+ mouseY = -mouseYrel + centerY;
704
+ coordX = KhanMath.roundTo(
705
+ 5,
706
+ mouseX / graph.scale[0] + graph.range[0][0],
707
+ );
708
+ coordY = KhanMath.roundTo(
709
+ 5,
710
+ graph.range[1][1] - mouseY / graph.scale[1],
711
+ );
712
+ }
713
+
714
+ const result = movablePoint.applyConstraint([coordX, coordY]);
715
+ return result;
716
+ };
717
+
718
+ // Using the passed coordinates, apply any constraints and return
719
+ // the closest coordinates that match the constraints.
720
+ movablePoint.applyConstraint = function (
721
+ coord: any,
722
+ extraConstraints: any,
723
+ override: any,
724
+ ) {
725
+ let newCoord = coord.slice();
726
+ const constraints: Record<string, any> = {};
727
+ if (override) {
728
+ $.extend(
729
+ constraints,
730
+ {
731
+ fixed: false,
732
+ constrainX: false,
733
+ constrainY: false,
734
+ fixedAngle: {},
735
+ fixedDistance: {},
736
+ },
737
+ extraConstraints,
738
+ );
739
+ } else {
740
+ $.extend(constraints, this.constraints, extraConstraints);
741
+ }
742
+
743
+ // constrain to vertical movement
744
+ if (constraints.constrainX) {
745
+ newCoord = [this.coord[0], coord[1]];
746
+
747
+ // constrain to horizontal movement
748
+ } else if (constraints.constrainY) {
749
+ newCoord = [coord[0], this.coord[1]];
750
+
751
+ // both distance and angle are constrained
752
+ } else if (
753
+ typeof constraints.fixedAngle.angle === "number" &&
754
+ typeof constraints.fixedDistance.dist === "number"
755
+ ) {
756
+ const vertex =
757
+ constraints.fixedAngle.vertex.coord ||
758
+ constraints.fixedAngle.vertex;
759
+ const ref =
760
+ constraints.fixedAngle.ref.coord ||
761
+ constraints.fixedAngle.ref;
762
+ const distPoint =
763
+ constraints.fixedDistance.point.coord ||
764
+ constraints.fixedDistance.point;
765
+
766
+ const constrainedAngle =
767
+ ((constraints.fixedAngle.angle +
768
+ GraphUtils.findAngle(ref, vertex)) *
769
+ Math.PI) /
770
+ 180;
771
+ const length = constraints.fixedDistance.dist;
772
+ newCoord[0] =
773
+ length * Math.cos(constrainedAngle) + distPoint[0];
774
+ newCoord[1] =
775
+ length * Math.sin(constrainedAngle) + distPoint[1];
776
+
777
+ // angle is constrained
778
+ } else if (typeof constraints.fixedAngle.angle === "number") {
779
+ const vertex =
780
+ constraints.fixedAngle.vertex.coord ||
781
+ constraints.fixedAngle.vertex;
782
+ const ref =
783
+ constraints.fixedAngle.ref.coord ||
784
+ constraints.fixedAngle.ref;
785
+
786
+ const constrainedAngle =
787
+ ((constraints.fixedAngle.angle +
788
+ GraphUtils.findAngle(ref, vertex)) *
789
+ Math.PI) /
790
+ 180;
791
+ const angle =
792
+ (GraphUtils.findAngle(coord, vertex) * Math.PI) / 180;
793
+ const distance = GraphUtils.getDistance(coord, vertex);
794
+ let length = distance * Math.cos(constrainedAngle - angle);
795
+ length = length < 1.0 ? 1.0 : length;
796
+ newCoord[0] = length * Math.cos(constrainedAngle) + vertex[0];
797
+ newCoord[1] = length * Math.sin(constrainedAngle) + vertex[1];
798
+
799
+ // distance is constrained
800
+ } else if (typeof constraints.fixedDistance.dist === "number") {
801
+ const distPoint =
802
+ constraints.fixedDistance.point.coord ||
803
+ constraints.fixedDistance.point;
804
+
805
+ let angle = GraphUtils.findAngle(coord, distPoint);
806
+ const length = constraints.fixedDistance.dist;
807
+ angle = (angle * Math.PI) / 180;
808
+ newCoord[0] = length * Math.cos(angle) + distPoint[0];
809
+ newCoord[1] = length * Math.sin(angle) + distPoint[1];
810
+
811
+ // point is fixed
812
+ } else if (constraints.fixed) {
813
+ newCoord = movablePoint.coord;
814
+ }
815
+ return newCoord;
816
+ };
817
+
818
+ movablePoint.coord = applySnapAndConstraints(movablePoint.coord);
819
+
820
+ const highlightScale = 2;
821
+
822
+ if (movablePoint.visible) {
823
+ graph.style(movablePoint.normalStyle, function () {
824
+ const radii = [
825
+ movablePoint.pointSize / graph.scale[0],
826
+ movablePoint.pointSize / graph.scale[1],
827
+ ];
828
+ const options = {
829
+ maxScale: highlightScale,
830
+ // Add in 2px of padding to avoid clipping at the edges.
831
+ padding: 2,
832
+ } as const;
833
+ movablePoint.visibleShape = new WrappedEllipse(
834
+ graph,
835
+ movablePoint.coord,
836
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'number[]' is not assignable to parameter of type '[number, number]'.
837
+ radii,
838
+ options,
839
+ );
840
+ movablePoint.visibleShape.attr(
841
+ _.omit(movablePoint.normalStyle, "scale"),
842
+ );
843
+ movablePoint.visibleShape.toFront();
844
+ });
845
+ }
846
+ movablePoint.normalStyle.scale = 1;
847
+ movablePoint.highlightStyle.scale = highlightScale;
848
+
849
+ if (movablePoint.vertexLabel) {
850
+ movablePoint.labeledVertex = this.label(
851
+ [0, 0],
852
+ "",
853
+ "center",
854
+ movablePoint.labelStyle,
855
+ );
856
+ }
857
+
858
+ movablePoint.drawLabel = function () {
859
+ if (movablePoint.vertexLabel) {
860
+ movablePoint.graph.labelVertex({
861
+ vertex: movablePoint.coord,
862
+ label: movablePoint.labeledVertex,
863
+ text: movablePoint.vertexLabel,
864
+ style: movablePoint.labelStyle,
865
+ });
866
+ }
867
+ };
868
+
869
+ movablePoint.drawLabel();
870
+
871
+ movablePoint.grab = function (offset) {
872
+ // The offset for the gesture. When provided, the movable point will
873
+ // track the mouse's position, plus this offset. This is typically
874
+ // used to lock the distance between a user's finger and the movable
875
+ // point, when dragging.
876
+ offset = offset || [0, 0];
877
+
878
+ $(document).bind(
879
+ "vmousemove.point vmouseup.point",
880
+ function (event) {
881
+ event.preventDefault();
882
+ movablePoint.dragging = true;
883
+ dragging = true;
884
+
885
+ // Adjust the target coordinate by accounting for the gesture's
886
+ // offset.
887
+ let coord = kvector.add(graph.getMouseCoord(event), offset);
888
+
889
+ coord = applySnapAndConstraints(coord);
890
+ let coordX = coord[0];
891
+ let coordY = coord[1];
892
+ let mouseX;
893
+ let mouseY;
894
+
895
+ if (event.type === "vmousemove") {
896
+ let doMove = true;
897
+ // The caller has the option of adding an onMove() method
898
+ // to the movablePoint object we return as a sort of event
899
+ // handler. By returning false from onMove(), the move
900
+ // can be vetoed, providing custom constraints on where
901
+ // the point can be moved. By returning array [x, y], the
902
+ // move can be overridden
903
+ if (_.isFunction(movablePoint.onMove)) {
904
+ const result = movablePoint.onMove(coordX, coordY);
905
+ if (result === false) {
906
+ doMove = false;
907
+ }
908
+ if (_.isArray(result)) {
909
+ coordX = result[0];
910
+ coordY = result[1];
911
+ }
912
+ }
913
+ // coord{X|Y} may have been modified by constraints or
914
+ // onMove handler; adjust mouse{X|Y} to match
915
+ mouseX = (coordX - graph.range[0][0]) * graph.scale[0];
916
+ mouseY = (-coordY + graph.range[1][1]) * graph.scale[1];
917
+
918
+ if (doMove) {
919
+ const point = graph.unscalePoint([mouseX, mouseY]);
920
+ movablePoint.visibleShape.moveTo(point);
921
+ movablePoint.mouseTarget.moveTo(point);
922
+ movablePoint.coord = [coordX, coordY];
923
+ movablePoint.updateLineEnds();
924
+ $(movablePoint).trigger("move");
925
+ }
926
+
927
+ movablePoint.drawLabel();
928
+ } else if (event.type === "vmouseup") {
929
+ $(document).unbind(".point");
930
+ movablePoint.dragging = false;
931
+ dragging = false;
932
+ if (_.isFunction(movablePoint.onMoveEnd)) {
933
+ const result = movablePoint.onMoveEnd(
934
+ coordX,
935
+ coordY,
936
+ );
937
+ if (_.isArray(result)) {
938
+ coordX = result[0];
939
+ coordY = result[1];
940
+ mouseX =
941
+ (coordX - graph.range[0][0]) *
942
+ graph.scale[0];
943
+ mouseY =
944
+ (-coordY + graph.range[1][1]) *
945
+ graph.scale[1];
946
+ const point = graph.unscalePoint([
947
+ mouseX,
948
+ mouseY,
949
+ ]);
950
+ movablePoint.visibleShape.moveTo(point);
951
+ movablePoint.mouseTarget.moveTo(point);
952
+ movablePoint.coord = [coordX, coordY];
953
+ }
954
+ }
955
+ if (!movablePoint.highlight) {
956
+ movablePoint.visibleShape.animate(
957
+ movablePoint.normalStyle,
958
+ 50,
959
+ );
960
+ if (movablePoint.onUnhighlight) {
961
+ movablePoint.onUnhighlight();
962
+ }
963
+ }
964
+ }
965
+ },
966
+ );
967
+ };
968
+
969
+ if (movablePoint.visible && !movablePoint.constraints.fixed) {
970
+ // the invisible shape in front of the point that gets mouse events
971
+ if (!movablePoint.mouseTarget) {
972
+ const radii = graph.unscaleVector(16);
973
+ const options = {
974
+ mouselayer: true,
975
+ padding: 0,
976
+ disableMouseEventsOnWrapper: true,
977
+ interactiveKindForTesting: "movable-point",
978
+ } as const;
979
+ movablePoint.mouseTarget = new WrappedEllipse(
980
+ graph,
981
+ movablePoint.coord,
982
+ radii,
983
+ options,
984
+ );
985
+ movablePoint.mouseTarget.attr({fill: "#000", opacity: 0.0});
986
+ }
987
+
988
+ const $mouseTarget = $(movablePoint.mouseTarget.getMouseTarget());
989
+ $mouseTarget.css("cursor", "move");
990
+ $mouseTarget.bind(
991
+ "vmousedown vmouseover vmouseout",
992
+ function (event) {
993
+ if (event.type === "vmouseover") {
994
+ movablePoint.highlight = true;
995
+ if (!dragging) {
996
+ movablePoint.visibleShape.animate(
997
+ movablePoint.highlightStyle,
998
+ 50,
999
+ );
1000
+ if (movablePoint.onHighlight) {
1001
+ movablePoint.onHighlight();
1002
+ }
1003
+ }
1004
+ } else if (event.type === "vmouseout") {
1005
+ movablePoint.highlight = false;
1006
+ if (!movablePoint.dragging && !dragging) {
1007
+ movablePoint.visibleShape.animate(
1008
+ movablePoint.normalStyle,
1009
+ 50,
1010
+ );
1011
+ if (movablePoint.onUnhighlight) {
1012
+ movablePoint.onUnhighlight();
1013
+ }
1014
+ }
1015
+ } else if (
1016
+ event.type === "vmousedown" &&
1017
+ (event.which === 1 || event.which === 0)
1018
+ ) {
1019
+ event.preventDefault();
1020
+
1021
+ // The offset between the cursor or finger and the initial
1022
+ // coordinates of the point. This is tracked so as to avoid
1023
+ // locking the moving point to the user's finger on touch
1024
+ // devices, which would obscure it, no matter how large we
1025
+ // made the touch target. Instead, we respect the offset at
1026
+ // which the point was grabbed for the entirety of the
1027
+ // gesture, if it's a touch-based interaction.
1028
+ const startCoord = movablePoint.coord;
1029
+ const startMouseCoord = graph.getMouseCoord(event);
1030
+ const isMouse = !("ontouchstart" in window);
1031
+ const touchOffset = isMouse
1032
+ ? [0, 0]
1033
+ : kvector.subtract(startCoord, startMouseCoord);
1034
+
1035
+ movablePoint.grab(touchOffset);
1036
+ }
1037
+ },
1038
+ );
1039
+ }
1040
+
1041
+ // Method to let the caller animate the point to a new position.
1042
+ // Useful as part of a hint to show the user the correct place
1043
+ // to put the point.
1044
+ movablePoint.moveTo = function (
1045
+ coordX: any,
1046
+ coordY: any,
1047
+ updateLines: any,
1048
+ ) {
1049
+ const distance = GraphUtils.getDistance(
1050
+ this.graph.scalePoint([coordX, coordY]),
1051
+ this.graph.scalePoint(this.coord),
1052
+ );
1053
+
1054
+ const time = distance * 5;
1055
+
1056
+ const cb =
1057
+ updateLines &&
1058
+ function (coord: any) {
1059
+ movablePoint.coord = coord;
1060
+ movablePoint.updateLineEnds();
1061
+ };
1062
+ this.visibleShape.animateTo([coordX, coordY], time, cb);
1063
+ this.mouseTarget.animateTo([coordX, coordY], time, cb);
1064
+ this.coord = [coordX, coordY];
1065
+ if (_.isFunction(this.onMove)) {
1066
+ this.onMove(coordX, coordY);
1067
+ }
1068
+ };
1069
+
1070
+ // After moving the point, call this to update all line segments
1071
+ // terminating at the point
1072
+ movablePoint.updateLineEnds = function () {
1073
+ $(this.lineStarts).each(function () {
1074
+ this.coordA = movablePoint.coord;
1075
+ this.transform();
1076
+ });
1077
+ $(this.lineEnds).each(function () {
1078
+ this.coordZ = movablePoint.coord;
1079
+ this.transform();
1080
+ });
1081
+ $(this.polygonVertices).each(function () {
1082
+ this.transform();
1083
+ });
1084
+ };
1085
+
1086
+ // Put the point at a new position without any checks, animation,
1087
+ // or callbacks
1088
+ movablePoint.setCoord = function (coord: any) {
1089
+ if (this.visible) {
1090
+ this.visibleShape.moveTo(coord);
1091
+ if (this.mouseTarget != null) {
1092
+ this.mouseTarget.moveTo(coord);
1093
+ }
1094
+ }
1095
+ this.coord = coord.slice();
1096
+ };
1097
+
1098
+ // Put the point at the new position, checking that it is
1099
+ // within the graph's bounds
1100
+ movablePoint.setCoordConstrained = function (coord: any) {
1101
+ this.setCoord(applySnapAndConstraints(coord));
1102
+ };
1103
+
1104
+ // Change z-order to back
1105
+ movablePoint.toBack = function () {
1106
+ if (this.visible) {
1107
+ if (this.mouseTarget != null) {
1108
+ this.mouseTarget.toBack();
1109
+ }
1110
+ this.visibleShape.toBack();
1111
+ }
1112
+ };
1113
+
1114
+ // Change z-order to front
1115
+ movablePoint.toFront = function () {
1116
+ if (this.visible) {
1117
+ if (this.mouseTarget != null) {
1118
+ this.mouseTarget.toFront();
1119
+ }
1120
+ this.visibleShape.toFront();
1121
+ }
1122
+ };
1123
+
1124
+ movablePoint.remove = function () {
1125
+ if (this.visibleShape) {
1126
+ this.visibleShape.remove();
1127
+ }
1128
+ if (this.mouseTarget) {
1129
+ this.mouseTarget.remove();
1130
+ }
1131
+ if (this.labeledVertex) {
1132
+ this.labeledVertex.remove();
1133
+ }
1134
+ };
1135
+
1136
+ return movablePoint;
1137
+ },
1138
+
1139
+ // MovableLineSegment is a line segment that can be dragged around the
1140
+ // screen. By attaching a smartPoint to each (or one) end, the ends can be
1141
+ // manipulated individually.
1142
+ //
1143
+ // To use with smartPoints, add the smartPoints first, then:
1144
+ // addMovableLineSegment({ pointA: smartPoint1, pointZ: smartPoint2 });
1145
+ // Or just one end:
1146
+ // addMovableLineSegment({ pointA: smartPoint, coordZ: [0, 0] });
1147
+ //
1148
+ // Include "fixed: true" in the options if you don't want the entire line
1149
+ // to be draggable (you can still use points to make the endpoints
1150
+ // draggable)
1151
+ //
1152
+ // The returned object includes the following properties/methods:
1153
+ //
1154
+ // - lineSegment.coordA / lineSegment.coordZ
1155
+ // The coordinates of each end of the line segment
1156
+ //
1157
+ // - lineSegment.transform(syncToPoints)
1158
+ // Repositions the line segment. Call after changing coordA
1159
+ // and/or coordZ, or pass syncToPoints = true to use the current
1160
+ // position of the corresponding smartPoints, if the segment was
1161
+ // defined using smartPoints
1162
+ //
1163
+ addMovableLineSegment: function (options: any) {
1164
+ const lineSegment = $.extend(
1165
+ {
1166
+ graph: this,
1167
+ coordA: [0, 0],
1168
+ coordZ: [1, 1],
1169
+ snapX: 0,
1170
+ snapY: 0,
1171
+ fixed: false,
1172
+ ticks: 0,
1173
+ normalStyle: {},
1174
+ highlightStyle: {
1175
+ stroke: KhanColors.INTERACTING,
1176
+ "stroke-width": 6,
1177
+ },
1178
+ labelStyle: {
1179
+ stroke: KhanColors.INTERACTIVE,
1180
+ color: KhanColors.INTERACTIVE,
1181
+ },
1182
+ highlight: false,
1183
+ dragging: false,
1184
+ tick: [],
1185
+ extendLine: false,
1186
+ extendRay: false,
1187
+ constraints: {
1188
+ fixed: false,
1189
+ constrainX: false,
1190
+ constrainY: false,
1191
+ },
1192
+ sideLabel: "",
1193
+ vertexLabels: [],
1194
+ numArrows: 0,
1195
+ numTicks: 0,
1196
+ movePointsWithLine: false,
1197
+ },
1198
+ options,
1199
+ );
1200
+
1201
+ const normalColor = lineSegment.fixed
1202
+ ? KhanColors.DYNAMIC
1203
+ : KhanColors.INTERACTIVE;
1204
+ lineSegment.normalStyle = _.extend(
1205
+ {},
1206
+ {
1207
+ "stroke-width": 2,
1208
+ stroke: normalColor,
1209
+ },
1210
+ options.normalStyle,
1211
+ );
1212
+ // arrowStyle should be kept in sync with styling of the line
1213
+ lineSegment.arrowStyle = _.extend({}, lineSegment.normalStyle, {
1214
+ color: lineSegment.normalStyle.stroke,
1215
+ });
1216
+
1217
+ // If the line segment is defined by movablePoints, coordA/coordZ are
1218
+ // owned by the points, otherwise they're owned by us
1219
+ if (options.pointA !== undefined) {
1220
+ lineSegment.coordA = options.pointA.coord;
1221
+ lineSegment.pointA.lineStarts.push(lineSegment);
1222
+ } else if (options.coordA !== undefined) {
1223
+ lineSegment.coordA = options.coordA.slice();
1224
+ }
1225
+
1226
+ if (options.pointZ !== undefined) {
1227
+ lineSegment.coordZ = options.pointZ.coord;
1228
+ lineSegment.pointZ.lineEnds.push(lineSegment);
1229
+ } else if (options.coordA !== undefined) {
1230
+ lineSegment.coordA = lineSegment.coordA.slice();
1231
+ }
1232
+
1233
+ const graph = lineSegment.graph;
1234
+
1235
+ graph.style(lineSegment.normalStyle);
1236
+ for (let i = 0; i < lineSegment.ticks; ++i) {
1237
+ lineSegment.tick[i] = InteractiveUtils.bogusShape;
1238
+ }
1239
+ let path = GraphUtils.unscaledSvgPath([
1240
+ [0, 0],
1241
+ [1, 0],
1242
+ ]);
1243
+ for (let i = 0; i < lineSegment.ticks; ++i) {
1244
+ const tickoffset =
1245
+ 0.5 - (lineSegment.ticks - 1 + i * 2) / graph.scale[0];
1246
+ // TODO(kevinb) figure out why path isn't being used
1247
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1248
+ path += GraphUtils.unscaledSvgPath([
1249
+ [tickoffset, -7],
1250
+ [tickoffset, 7],
1251
+ ]);
1252
+ }
1253
+
1254
+ options = {
1255
+ thickness: Math.max(
1256
+ lineSegment.normalStyle["stroke-width"],
1257
+ lineSegment.highlightStyle["stroke-width"],
1258
+ ),
1259
+ };
1260
+ lineSegment.visibleLine = new WrappedLine(
1261
+ graph,
1262
+ [0, 0],
1263
+ [1, 0],
1264
+ options,
1265
+ );
1266
+ lineSegment.visibleLine.attr(lineSegment.normalStyle);
1267
+
1268
+ // Add mouse target
1269
+ if (!lineSegment.fixed) {
1270
+ const options = {
1271
+ thickness: 30,
1272
+ mouselayer: true,
1273
+ interactiveKindForTesting: "movable-line",
1274
+ } as const;
1275
+ lineSegment.mouseTarget = new WrappedLine(
1276
+ graph,
1277
+ [0, 0],
1278
+ [1, 0],
1279
+ options,
1280
+ );
1281
+ lineSegment.mouseTarget.attr({fill: "#000", opacity: 0.0});
1282
+ }
1283
+
1284
+ // Reposition the line segment. Call after changing coordA and/or
1285
+ // coordZ, or pass syncToPoints = true to use the current position of
1286
+ // the corresponding movablePoints, if the segment was defined using
1287
+ // movablePoints
1288
+ lineSegment.transform = function (syncToPoints) {
1289
+ if (syncToPoints) {
1290
+ if (typeof this.pointA === "object") {
1291
+ this.coordA = this.pointA.coord;
1292
+ }
1293
+ if (typeof this.pointZ === "object") {
1294
+ this.coordZ = this.pointZ.coord;
1295
+ }
1296
+ }
1297
+
1298
+ const getScaledAngle = function (line) {
1299
+ const scaledA = line.graph.scalePoint(line.coordA);
1300
+ const scaledZ = line.graph.scalePoint(line.coordZ);
1301
+ return kvector.polarDegFromCart(
1302
+ kvector.subtract(scaledZ, scaledA),
1303
+ )[1];
1304
+ };
1305
+
1306
+ const getClipPoint = function (graph: any, coord, angle: number) {
1307
+ graph = lineSegment.graph;
1308
+ const xExtent = graph.range[0][1] - graph.range[0][0];
1309
+ const yExtent = graph.range[1][1] - graph.range[1][0];
1310
+
1311
+ const distance = xExtent + yExtent;
1312
+ const angleVec = graph.unscaleVector(
1313
+ kvector.cartFromPolarDeg(1, angle),
1314
+ );
1315
+ const distVec = kvector.scale(
1316
+ kvector.normalize(angleVec),
1317
+ distance,
1318
+ );
1319
+ const farCoord = kvector.add(coord, distVec);
1320
+ const scaledAngle = kvector.polarDegFromCart(angleVec)[1];
1321
+ const clipPoint = graph.constrainToBoundsOnAngle(
1322
+ farCoord,
1323
+ 4,
1324
+ (scaledAngle * Math.PI) / 180,
1325
+ );
1326
+ return clipPoint;
1327
+ };
1328
+
1329
+ const angle = getScaledAngle(this);
1330
+ let start = this.coordA;
1331
+ let end = this.coordZ;
1332
+
1333
+ // Extend start, end if necessary (i.e., if not a line segment)
1334
+ if (this.extendLine) {
1335
+ start = getClipPoint(graph, start, 360 - angle);
1336
+ end = getClipPoint(graph, end, (540 - angle) % 360);
1337
+ } else if (this.extendRay) {
1338
+ end = getClipPoint(graph, start, 360 - angle);
1339
+ }
1340
+
1341
+ const elements = [this.visibleLine];
1342
+ if (!this.fixed) {
1343
+ elements.push(this.mouseTarget);
1344
+ }
1345
+ _.each(elements, function (element) {
1346
+ element.moveTo(start, end);
1347
+ });
1348
+
1349
+ const createArrow = function (graph: any, style) {
1350
+ const center = [0.75, 0];
1351
+ let points = [
1352
+ [-3, 4],
1353
+ [-2.75, 2.5],
1354
+ [0, 0.25],
1355
+ center,
1356
+ [0, -0.25],
1357
+ [-2.75, -2.5],
1358
+ [-3, -4],
1359
+ ];
1360
+
1361
+ const scale = 1.4;
1362
+ points = _.map(points, function (point) {
1363
+ const pv = kvector.subtract(point, center);
1364
+ const pvScaled = kvector.scale(pv, scale);
1365
+ return kvector.add(center, pvScaled);
1366
+ });
1367
+
1368
+ const createCubicPath = function (points: any) {
1369
+ let path = "M" + points[0][0] + " " + points[0][1];
1370
+ for (let i = 1; i < points.length; i += 3) {
1371
+ path +=
1372
+ "C" +
1373
+ points[i][0] +
1374
+ " " +
1375
+ points[i][1] +
1376
+ " " +
1377
+ points[i + 1][0] +
1378
+ " " +
1379
+ points[i + 1][1] +
1380
+ " " +
1381
+ points[i + 2][0] +
1382
+ " " +
1383
+ points[i + 2][1];
1384
+ }
1385
+ return path;
1386
+ };
1387
+
1388
+ const unscaledPoints = _.map(points, graph.unscalePoint);
1389
+ const options = {
1390
+ center: graph.unscalePoint(center),
1391
+ createPath: createCubicPath,
1392
+ } as const;
1393
+ const arrowHead = new WrappedPath(
1394
+ graph,
1395
+ unscaledPoints,
1396
+ options,
1397
+ );
1398
+ arrowHead.attr(
1399
+ _.extend(
1400
+ {
1401
+ "stroke-linejoin": "round",
1402
+ "stroke-linecap": "round",
1403
+ "stroke-dasharray": "",
1404
+ },
1405
+ style,
1406
+ ),
1407
+ );
1408
+
1409
+ // Add custom function for transforming arrowheads that
1410
+ // accounts for center, scaling, etc.
1411
+ arrowHead.toCoordAtAngle = function (
1412
+ coord: Coord,
1413
+ angle: number,
1414
+ ): void {
1415
+ const clipPoint = graph.scalePoint(
1416
+ getClipPoint(graph, coord, angle),
1417
+ );
1418
+ const do3dTransform = getCanUse3dTransform();
1419
+ arrowHead.transform(
1420
+ "translateX(" +
1421
+ (clipPoint[0] + scale * center[0]) +
1422
+ "px) " +
1423
+ "translateY(" +
1424
+ (clipPoint[1] + scale * center[1]) +
1425
+ "px) " +
1426
+ (do3dTransform ? "translateZ(0) " : "") +
1427
+ "rotate(" +
1428
+ (360 - KhanMath.bound(angle)) +
1429
+ "deg)",
1430
+ );
1431
+ };
1432
+
1433
+ return arrowHead;
1434
+ };
1435
+
1436
+ // Add arrows
1437
+ if (this._arrows == null) {
1438
+ this._arrows = [];
1439
+
1440
+ if (this.extendLine) {
1441
+ this._arrows.push(createArrow(graph, this.normalStyle));
1442
+ this._arrows.push(createArrow(graph, this.normalStyle));
1443
+ } else if (this.extendRay) {
1444
+ this._arrows.push(createArrow(graph, this.normalStyle));
1445
+ }
1446
+ }
1447
+
1448
+ const coordForArrow = [this.coordA, this.coordZ];
1449
+ const angleForArrow = [360 - angle, (540 - angle) % 360];
1450
+ _.each(this._arrows, function (arrow, i) {
1451
+ arrow.toCoordAtAngle(coordForArrow[i], angleForArrow[i]);
1452
+ });
1453
+
1454
+ // Temporary objects: array of SVG nodes that get recreated on drag
1455
+ _.invoke(this.temp, "remove");
1456
+ this.temp = [];
1457
+
1458
+ const isClockwise =
1459
+ this.coordA[0] < this.coordZ[0] ||
1460
+ (this.coordA[0] === this.coordZ[0] &&
1461
+ this.coordA[1] > this.coordZ[1]);
1462
+
1463
+ // Update side label
1464
+ if (this.sideLabel) {
1465
+ this.temp.push(
1466
+ this.graph.labelSide({
1467
+ point1: this.coordA,
1468
+ point2: this.coordZ,
1469
+ label: this.labeledSide,
1470
+ text: this.sideLabel,
1471
+ numArrows: this.numArrows,
1472
+ numTicks: this.numTicks,
1473
+ clockwise: isClockwise,
1474
+ style: this.labelStyle,
1475
+ }),
1476
+ );
1477
+ }
1478
+
1479
+ // Update vertex labels
1480
+ if (this.vertexLabels.length) {
1481
+ this.graph.labelVertex({
1482
+ vertex: this.coordA,
1483
+ point3: this.coordZ,
1484
+ label: this.labeledVertices[0],
1485
+ text: this.vertexLabels[0],
1486
+ clockwise: isClockwise,
1487
+ style: this.labelStyle,
1488
+ });
1489
+
1490
+ this.graph.labelVertex({
1491
+ point1: this.coordA,
1492
+ vertex: this.coordZ,
1493
+ label: this.labeledVertices[1],
1494
+ text: this.vertexLabels[1],
1495
+ clockwise: isClockwise,
1496
+ style: this.labelStyle,
1497
+ });
1498
+ }
1499
+
1500
+ this.temp = _.flatten(this.temp);
1501
+ };
1502
+
1503
+ // Change z-order to back;
1504
+ lineSegment.toBack = function () {
1505
+ if (!lineSegment.fixed) {
1506
+ lineSegment.mouseTarget.toBack();
1507
+ }
1508
+ lineSegment.visibleLine.toBack();
1509
+ };
1510
+
1511
+ // Change z-order to front
1512
+ lineSegment.toFront = function () {
1513
+ if (!lineSegment.fixed) {
1514
+ lineSegment.mouseTarget.toFront();
1515
+ }
1516
+ lineSegment.visibleLine.toFront();
1517
+ };
1518
+
1519
+ lineSegment.remove = function () {
1520
+ if (!lineSegment.fixed) {
1521
+ lineSegment.mouseTarget.remove();
1522
+ }
1523
+ lineSegment.visibleLine.remove();
1524
+ if (lineSegment.labeledSide) {
1525
+ lineSegment.labeledSide.remove();
1526
+ }
1527
+ if (lineSegment.labeledVertices) {
1528
+ _.invoke(lineSegment.labeledVertices, "remove");
1529
+ }
1530
+ if (lineSegment._arrows) {
1531
+ _.invoke(lineSegment._arrows, "remove");
1532
+ }
1533
+ if (lineSegment.temp.length) {
1534
+ _.invoke(lineSegment.temp, "remove");
1535
+ }
1536
+ };
1537
+
1538
+ lineSegment.hide = function () {
1539
+ lineSegment.visibleLine.hide();
1540
+ if (lineSegment.temp.length) {
1541
+ _.invoke(lineSegment.temp, "hide");
1542
+ }
1543
+ if (lineSegment._arrows) {
1544
+ _.invoke(lineSegment._arrows, "hide");
1545
+ }
1546
+ };
1547
+
1548
+ lineSegment.show = function () {
1549
+ lineSegment.visibleLine.show();
1550
+ if (lineSegment.temp.length) {
1551
+ _.invoke(lineSegment.temp, "show");
1552
+ }
1553
+ if (lineSegment._arrows) {
1554
+ _.invoke(lineSegment._arrows, "show");
1555
+ }
1556
+ };
1557
+
1558
+ if (lineSegment.sideLabel) {
1559
+ lineSegment.labeledSide = this.label(
1560
+ [0, 0],
1561
+ "",
1562
+ "center",
1563
+ lineSegment.labelStyle,
1564
+ );
1565
+ }
1566
+
1567
+ if (lineSegment.vertexLabels.length) {
1568
+ lineSegment.labeledVertices = _.map(
1569
+ lineSegment.vertexLabels,
1570
+ function (label) {
1571
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
1572
+ return this.label(
1573
+ [0, 0],
1574
+ "",
1575
+ "center",
1576
+ lineSegment.labelStyle,
1577
+ );
1578
+ },
1579
+ this,
1580
+ );
1581
+ }
1582
+
1583
+ if (!lineSegment.fixed && !lineSegment.constraints.fixed) {
1584
+ const $mouseTarget = $(lineSegment.mouseTarget.getMouseTarget());
1585
+ $mouseTarget.css("cursor", "move");
1586
+ $mouseTarget.bind(
1587
+ "vmousedown vmouseover vmouseout",
1588
+ function (event) {
1589
+ if (event.type === "vmouseover") {
1590
+ if (!dragging) {
1591
+ lineSegment.highlight = true;
1592
+ lineSegment.visibleLine.animate(
1593
+ lineSegment.highlightStyle,
1594
+ 50,
1595
+ );
1596
+ lineSegment.arrowStyle = _.extend(
1597
+ {},
1598
+ lineSegment.arrowStyle,
1599
+ {
1600
+ color: lineSegment.highlightStyle.stroke,
1601
+ stroke: lineSegment.highlightStyle.stroke,
1602
+ },
1603
+ );
1604
+ lineSegment.transform();
1605
+ }
1606
+ } else if (event.type === "vmouseout") {
1607
+ lineSegment.highlight = false;
1608
+ if (!lineSegment.dragging) {
1609
+ lineSegment.visibleLine.animate(
1610
+ lineSegment.normalStyle,
1611
+ 50,
1612
+ );
1613
+ lineSegment.arrowStyle = _.extend(
1614
+ {},
1615
+ lineSegment.arrowStyle,
1616
+ {
1617
+ color: lineSegment.normalStyle.stroke,
1618
+ stroke: lineSegment.normalStyle.stroke,
1619
+ },
1620
+ );
1621
+ lineSegment.transform();
1622
+ }
1623
+ } else if (
1624
+ event.type === "vmousedown" &&
1625
+ (event.which === 1 || event.which === 0)
1626
+ ) {
1627
+ event.preventDefault();
1628
+ let coordX =
1629
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
1630
+ (event.pageX -
1631
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
1632
+ $(graph.raphael.canvas.parentNode).offset()
1633
+ .left) /
1634
+ graph.scale[0] +
1635
+ graph.range[0][0];
1636
+ let coordY =
1637
+ graph.range[1][1] -
1638
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
1639
+ (event.pageY -
1640
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
1641
+ $(graph.raphael.canvas.parentNode).offset()
1642
+ .top) /
1643
+ graph.scale[1];
1644
+ if (lineSegment.snapX > 0) {
1645
+ coordX =
1646
+ Math.round(coordX / lineSegment.snapX) *
1647
+ lineSegment.snapX;
1648
+ }
1649
+ if (lineSegment.snapY > 0) {
1650
+ coordY =
1651
+ Math.round(coordY / lineSegment.snapY) *
1652
+ lineSegment.snapY;
1653
+ }
1654
+ const mouseOffsetA = [
1655
+ lineSegment.coordA[0] - coordX,
1656
+ lineSegment.coordA[1] - coordY,
1657
+ ];
1658
+ const mouseOffsetZ = [
1659
+ lineSegment.coordZ[0] - coordX,
1660
+ lineSegment.coordZ[1] - coordY,
1661
+ ];
1662
+
1663
+ const offsetLeft = -Math.min(
1664
+ graph.scaleVector(mouseOffsetA)[0],
1665
+ graph.scaleVector(mouseOffsetZ)[0],
1666
+ );
1667
+ const offsetRight = Math.max(
1668
+ graph.scaleVector(mouseOffsetA)[0],
1669
+ graph.scaleVector(mouseOffsetZ)[0],
1670
+ );
1671
+ const offsetTop = Math.max(
1672
+ graph.scaleVector(mouseOffsetA)[1],
1673
+ graph.scaleVector(mouseOffsetZ)[1],
1674
+ );
1675
+ const offsetBottom = -Math.min(
1676
+ graph.scaleVector(mouseOffsetA)[1],
1677
+ graph.scaleVector(mouseOffsetZ)[1],
1678
+ );
1679
+
1680
+ $(document).bind(
1681
+ "vmousemove.lineSegment vmouseup.lineSegment",
1682
+ function (event) {
1683
+ event.preventDefault();
1684
+ lineSegment.dragging = true;
1685
+ dragging = true;
1686
+
1687
+ let mouseX =
1688
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
1689
+ event.pageX -
1690
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
1691
+ $(graph.raphael.canvas.parentNode).offset()
1692
+ .left;
1693
+ let mouseY =
1694
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
1695
+ event.pageY -
1696
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
1697
+ $(graph.raphael.canvas.parentNode).offset()
1698
+ .top;
1699
+ // no part of the line segment can go beyond 10
1700
+ // pixels from the edge
1701
+ mouseX = Math.max(
1702
+ offsetLeft + 10,
1703
+ Math.min(
1704
+ graph.xpixels - 10 - offsetRight,
1705
+ mouseX,
1706
+ ),
1707
+ );
1708
+ mouseY = Math.max(
1709
+ offsetTop + 10,
1710
+ Math.min(
1711
+ graph.ypixels - 10 - offsetBottom,
1712
+ mouseY,
1713
+ ),
1714
+ );
1715
+
1716
+ let coordX =
1717
+ mouseX / graph.scale[0] + graph.range[0][0];
1718
+ let coordY =
1719
+ graph.range[1][1] - mouseY / graph.scale[1];
1720
+ if (lineSegment.snapX > 0) {
1721
+ coordX =
1722
+ Math.round(coordX / lineSegment.snapX) *
1723
+ lineSegment.snapX;
1724
+ }
1725
+ if (lineSegment.snapY > 0) {
1726
+ coordY =
1727
+ Math.round(coordY / lineSegment.snapY) *
1728
+ lineSegment.snapY;
1729
+ }
1730
+
1731
+ if (event.type === "vmousemove") {
1732
+ if (lineSegment.constraints.constrainX) {
1733
+ coordX =
1734
+ lineSegment.coordA[0] -
1735
+ mouseOffsetA[0];
1736
+ }
1737
+ if (lineSegment.constraints.constrainY) {
1738
+ coordY =
1739
+ lineSegment.coordA[1] -
1740
+ mouseOffsetA[1];
1741
+ }
1742
+ const dX =
1743
+ coordX +
1744
+ mouseOffsetA[0] -
1745
+ lineSegment.coordA[0];
1746
+ const dY =
1747
+ coordY +
1748
+ mouseOffsetA[1] -
1749
+ lineSegment.coordA[1];
1750
+ lineSegment.coordA = [
1751
+ coordX + mouseOffsetA[0],
1752
+ coordY + mouseOffsetA[1],
1753
+ ];
1754
+ lineSegment.coordZ = [
1755
+ coordX + mouseOffsetZ[0],
1756
+ coordY + mouseOffsetZ[1],
1757
+ ];
1758
+ lineSegment.transform();
1759
+
1760
+ if (lineSegment.movePointsWithLine) {
1761
+ // If the points are movablePoints, adjust
1762
+ // their coordinates when the line itself is
1763
+ // dragged
1764
+ if (
1765
+ typeof lineSegment.pointA ===
1766
+ "object"
1767
+ ) {
1768
+ lineSegment.pointA.setCoord([
1769
+ lineSegment.pointA.coord[0] +
1770
+ dX,
1771
+ lineSegment.pointA.coord[1] +
1772
+ dY,
1773
+ ]);
1774
+ }
1775
+ if (
1776
+ typeof lineSegment.pointZ ===
1777
+ "object"
1778
+ ) {
1779
+ lineSegment.pointZ.setCoord([
1780
+ lineSegment.pointZ.coord[0] +
1781
+ dX,
1782
+ lineSegment.pointZ.coord[1] +
1783
+ dY,
1784
+ ]);
1785
+ }
1786
+ }
1787
+
1788
+ if (_.isFunction(lineSegment.onMove)) {
1789
+ lineSegment.onMove(dX, dY);
1790
+ }
1791
+ } else if (event.type === "vmouseup") {
1792
+ $(document).unbind(".lineSegment");
1793
+ lineSegment.dragging = false;
1794
+ dragging = false;
1795
+ if (!lineSegment.highlight) {
1796
+ lineSegment.visibleLine.animate(
1797
+ lineSegment.normalStyle,
1798
+ 50,
1799
+ );
1800
+ lineSegment.arrowStyle = _.extend(
1801
+ {},
1802
+ lineSegment.arrowStyle,
1803
+ {
1804
+ color: lineSegment.normalStyle
1805
+ .stroke,
1806
+ stroke: lineSegment.normalStyle
1807
+ .stroke,
1808
+ },
1809
+ );
1810
+ lineSegment.transform();
1811
+ }
1812
+ if (_.isFunction(lineSegment.onMoveEnd)) {
1813
+ lineSegment.onMoveEnd();
1814
+ }
1815
+ }
1816
+
1817
+ $(lineSegment).trigger("move");
1818
+ },
1819
+ );
1820
+ }
1821
+ },
1822
+ );
1823
+ }
1824
+
1825
+ if (lineSegment.pointA !== undefined) {
1826
+ lineSegment.pointA.toFront();
1827
+ }
1828
+ if (lineSegment.pointZ !== undefined) {
1829
+ lineSegment.pointZ.toFront();
1830
+ }
1831
+ lineSegment.transform();
1832
+ return lineSegment;
1833
+ },
1834
+
1835
+ // MovablePolygon is a polygon that can be dragged around the screen.
1836
+ // By attaching a smartPoint to each vertex, the points can be
1837
+ // manipulated individually.
1838
+ //
1839
+ // To use with smartPoints, add the smartPoints first, then:
1840
+ // addMovablePolygon({points: [...]});
1841
+ //
1842
+ // Include "fixed: true" in the options if you don't want the entire
1843
+ // polygon to be draggable (you can still use points to make the
1844
+ // vertices draggable)
1845
+ //
1846
+ // The returned object includes the following properties/methods:
1847
+ //
1848
+ // - polygon.points
1849
+ // The polygon's dynamic smartPoints and static coordinates, mixed.
1850
+ //
1851
+ // - polygon.coords
1852
+ // The polygon's current coordinates (generated, don't edit).
1853
+ //
1854
+ // - polygon.transform()
1855
+ // Repositions the polygon. Call after changing any points.
1856
+ //
1857
+ addMovablePolygon: function (options) {
1858
+ const graphie = this;
1859
+
1860
+ const polygon = $.extend(
1861
+ {
1862
+ snapX: 0,
1863
+ snapY: 0,
1864
+ fixed: false,
1865
+ constrainToGraph: true,
1866
+ normalStyle: {},
1867
+ highlightStyle: {
1868
+ stroke: KhanColors.INTERACTING,
1869
+ "stroke-width": 2,
1870
+ fill: KhanColors.INTERACTING,
1871
+ "fill-opacity": 0.05,
1872
+ },
1873
+ pointHighlightStyle: {
1874
+ fill: KhanColors.INTERACTING,
1875
+ stroke: KhanColors.INTERACTING,
1876
+ },
1877
+ labelStyle: {
1878
+ stroke: KhanColors.DYNAMIC,
1879
+ "stroke-width": 1,
1880
+ color: KhanColors.DYNAMIC,
1881
+ },
1882
+ angleLabels: [],
1883
+ showRightAngleMarkers: [],
1884
+ sideLabels: [],
1885
+ vertexLabels: [],
1886
+ numArcs: [],
1887
+ numArrows: [],
1888
+ numTicks: [],
1889
+ updateOnPointMove: true,
1890
+ closed: true,
1891
+ },
1892
+ _.omit(options, "points"),
1893
+ );
1894
+
1895
+ const normalColor = polygon.fixed
1896
+ ? KhanColors.DYNAMIC
1897
+ : KhanColors.INTERACTIVE;
1898
+ polygon.normalStyle = _.extend(
1899
+ {
1900
+ "stroke-width": 2,
1901
+ "fill-opacity": 0,
1902
+ fill: normalColor,
1903
+ stroke: normalColor,
1904
+ },
1905
+ options.normalStyle,
1906
+ );
1907
+
1908
+ // don't deep copy the points array with $.extend;
1909
+ // we may want to append to it later for click-to-add-points
1910
+ polygon.points = options.points;
1911
+
1912
+ const isPoint = function (coordOrPoint: any) {
1913
+ return !_.isArray(coordOrPoint);
1914
+ };
1915
+
1916
+ polygon.update = function () {
1917
+ const n = polygon.points.length;
1918
+
1919
+ // Update coords
1920
+ polygon.coords = _.map(polygon.points, function (coordOrPoint, i) {
1921
+ if (isPoint(coordOrPoint)) {
1922
+ return coordOrPoint.coord;
1923
+ }
1924
+ return coordOrPoint;
1925
+ });
1926
+
1927
+ // Calculate bounding box
1928
+ polygon.left = _.min(_.pluck(polygon.coords, 0));
1929
+ polygon.right = _.max(_.pluck(polygon.coords, 0));
1930
+ polygon.top = _.max(_.pluck(polygon.coords, 1));
1931
+ polygon.bottom = _.min(_.pluck(polygon.coords, 1));
1932
+
1933
+ let scaledCoords = _.map(polygon.coords, function (coord) {
1934
+ return graphie.scalePoint(coord);
1935
+ });
1936
+
1937
+ // Create path
1938
+ if (polygon.closed) {
1939
+ scaledCoords.push(true);
1940
+ } else {
1941
+ // For open polygons, concatenate a reverse of the path,
1942
+ // to remove the inside area of the path, which would
1943
+ // otherwise be clickable (even if the closing line segment
1944
+ // wasn't drawn
1945
+ scaledCoords = scaledCoords.concat(
1946
+ _.clone(scaledCoords).reverse(),
1947
+ );
1948
+ }
1949
+ polygon.path = GraphUtils.unscaledSvgPath(scaledCoords);
1950
+
1951
+ // Temporary objects
1952
+ _.invoke(polygon.temp, "remove");
1953
+ polygon.temp = [];
1954
+
1955
+ const isClockwise = clockwise(polygon.coords);
1956
+
1957
+ // Update angle labels
1958
+ if (
1959
+ polygon.angleLabels.length ||
1960
+ polygon.showRightAngleMarkers.length
1961
+ ) {
1962
+ _.each(polygon.labeledAngles, function (label, i) {
1963
+ polygon.temp.push(
1964
+ graphie.labelAngle({
1965
+ point1: polygon.coords[(i - 1 + n) % n],
1966
+ vertex: polygon.coords[i],
1967
+ point3: polygon.coords[(i + 1) % n],
1968
+ label: label,
1969
+ text: polygon.angleLabels[i],
1970
+ showRightAngleMarker:
1971
+ polygon.showRightAngleMarkers[i],
1972
+ numArcs: polygon.numArcs[i],
1973
+ clockwise: isClockwise,
1974
+ style: polygon.labelStyle,
1975
+ }),
1976
+ );
1977
+ });
1978
+ }
1979
+
1980
+ // Update side labels
1981
+ if (polygon.sideLabels.length) {
1982
+ _.each(polygon.labeledSides, function (label, i) {
1983
+ polygon.temp.push(
1984
+ graphie.labelSide({
1985
+ point1: polygon.coords[i],
1986
+ point2: polygon.coords[(i + 1) % n],
1987
+ label: label,
1988
+ text: polygon.sideLabels[i],
1989
+ numArrows: polygon.numArrows[i],
1990
+ numTicks: polygon.numTicks[i],
1991
+ clockwise: isClockwise,
1992
+ style: polygon.labelStyle,
1993
+ }),
1994
+ );
1995
+ });
1996
+ }
1997
+
1998
+ // Update vertex labels
1999
+ if (polygon.vertexLabels.length) {
2000
+ _.each(polygon.labeledVertices, function (label, i) {
2001
+ graphie.labelVertex({
2002
+ point1: polygon.coords[(i - 1 + n) % n],
2003
+ vertex: polygon.coords[i],
2004
+ point3: polygon.coords[(i + 1) % n],
2005
+ label: label,
2006
+ text: polygon.vertexLabels[i],
2007
+ clockwise: isClockwise,
2008
+ style: polygon.labelStyle,
2009
+ });
2010
+ });
2011
+ }
2012
+
2013
+ polygon.temp = _.flatten(polygon.temp);
2014
+ };
2015
+
2016
+ polygon.transform = function () {
2017
+ polygon.update();
2018
+
2019
+ polygon.visibleShape.attr({path: polygon.path});
2020
+
2021
+ if (!polygon.fixed) {
2022
+ polygon.mouseTarget.attr({path: polygon.path});
2023
+ }
2024
+ };
2025
+
2026
+ polygon.remove = function () {
2027
+ polygon.visibleShape.remove();
2028
+
2029
+ if (!polygon.fixed) {
2030
+ polygon.mouseTarget.remove();
2031
+ }
2032
+
2033
+ if (polygon.labeledAngles) {
2034
+ _.invoke(polygon.labeledAngles, "remove");
2035
+ }
2036
+
2037
+ if (polygon.labeledSides) {
2038
+ _.invoke(polygon.labeledSides, "remove");
2039
+ }
2040
+
2041
+ if (polygon.labeledVertices) {
2042
+ _.invoke(polygon.labeledVertices, "remove");
2043
+ }
2044
+
2045
+ if (polygon.temp.length) {
2046
+ _.invoke(polygon.temp, "remove");
2047
+ }
2048
+ };
2049
+
2050
+ polygon.toBack = function () {
2051
+ if (!polygon.fixed) {
2052
+ polygon.mouseTarget.toBack();
2053
+ }
2054
+
2055
+ polygon.visibleShape.toBack();
2056
+ };
2057
+
2058
+ polygon.toFront = function () {
2059
+ if (!polygon.fixed) {
2060
+ polygon.mouseTarget.toFront();
2061
+ }
2062
+
2063
+ polygon.visibleShape.toFront();
2064
+ };
2065
+
2066
+ // Setup
2067
+
2068
+ if (polygon.updateOnPointMove) {
2069
+ _.each(_.filter(polygon.points, isPoint), function (coordOrPoint) {
2070
+ coordOrPoint.polygonVertices.push(polygon);
2071
+ });
2072
+ }
2073
+
2074
+ polygon.coords = new Array(polygon.points.length);
2075
+
2076
+ if (polygon.angleLabels.length) {
2077
+ const numLabels = Math.max(
2078
+ polygon.angleLabels.length,
2079
+ polygon.showRightAngleMarkers.length,
2080
+ );
2081
+ polygon.labeledAngles = _.times(
2082
+ numLabels,
2083
+ function () {
2084
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
2085
+ return this.label([0, 0], "", "center", polygon.labelStyle);
2086
+ },
2087
+ this,
2088
+ );
2089
+ }
2090
+
2091
+ if (polygon.sideLabels.length) {
2092
+ polygon.labeledSides = _.map(
2093
+ polygon.sideLabels,
2094
+ function (label) {
2095
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
2096
+ return this.label([0, 0], "", "center", polygon.labelStyle);
2097
+ },
2098
+ this,
2099
+ );
2100
+ }
2101
+
2102
+ if (polygon.vertexLabels.length) {
2103
+ polygon.labeledVertices = _.map(
2104
+ polygon.vertexLabels,
2105
+ function (label) {
2106
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
2107
+ return this.label([0, 0], "", "center", polygon.labelStyle);
2108
+ },
2109
+ this,
2110
+ );
2111
+ }
2112
+
2113
+ polygon.update();
2114
+
2115
+ polygon.visibleShape = graphie.raphael.path(polygon.path);
2116
+ polygon.visibleShape.attr(polygon.normalStyle);
2117
+
2118
+ if (!polygon.fixed) {
2119
+ polygon.mouseTarget = graphie.mouselayer.path(polygon.path);
2120
+ polygon.mouseTarget.attr({
2121
+ fill: "#000",
2122
+ opacity: 0,
2123
+ cursor: "move",
2124
+ });
2125
+
2126
+ $(polygon.mouseTarget[0]).bind(
2127
+ "vmousedown vmouseover vmouseout",
2128
+ function (event) {
2129
+ if (event.type === "vmouseover") {
2130
+ if (!dragging || polygon.dragging) {
2131
+ polygon.highlight = true;
2132
+ polygon.visibleShape.animate(
2133
+ polygon.highlightStyle,
2134
+ 50,
2135
+ );
2136
+ _.each(
2137
+ _.filter(polygon.points, isPoint),
2138
+ function (point) {
2139
+ point.visibleShape.animate(
2140
+ polygon.pointHighlightStyle,
2141
+ 50,
2142
+ );
2143
+ },
2144
+ );
2145
+ }
2146
+ } else if (event.type === "vmouseout") {
2147
+ polygon.highlight = false;
2148
+ if (!polygon.dragging) {
2149
+ polygon.visibleShape.animate(
2150
+ polygon.normalStyle,
2151
+ 50,
2152
+ );
2153
+ const points = _.filter(polygon.points, isPoint);
2154
+ if (!_.any(_.pluck(points, "dragging"))) {
2155
+ _.each(points, function (point) {
2156
+ point.visibleShape.animate(
2157
+ point.normalStyle,
2158
+ 50,
2159
+ );
2160
+ });
2161
+ }
2162
+ }
2163
+ } else if (
2164
+ event.type === "vmousedown" &&
2165
+ (event.which === 1 || event.which === 0)
2166
+ ) {
2167
+ event.preventDefault();
2168
+
2169
+ _.each(
2170
+ _.filter(polygon.points, isPoint),
2171
+ function (point) {
2172
+ point.dragging = true;
2173
+ },
2174
+ );
2175
+
2176
+ let startX =
2177
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
2178
+ (event.pageX -
2179
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
2180
+ $(graphie.raphael.canvas.parentNode).offset()
2181
+ .left) /
2182
+ graphie.scale[0] +
2183
+ graphie.range[0][0];
2184
+ let startY =
2185
+ graphie.range[1][1] -
2186
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
2187
+ (event.pageY -
2188
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
2189
+ $(graphie.raphael.canvas.parentNode).offset()
2190
+ .top) /
2191
+ graphie.scale[1];
2192
+ if (polygon.snapX > 0) {
2193
+ startX =
2194
+ Math.round(startX / polygon.snapX) *
2195
+ polygon.snapX;
2196
+ }
2197
+ if (polygon.snapY > 0) {
2198
+ startY =
2199
+ Math.round(startY / polygon.snapY) *
2200
+ polygon.snapY;
2201
+ }
2202
+ let lastX = startX;
2203
+ let lastY = startY;
2204
+
2205
+ const polygonCoords = polygon.coords.slice();
2206
+
2207
+ const offsetLeft =
2208
+ (startX - polygon.left) * graphie.scale[0];
2209
+ const offsetRight =
2210
+ (polygon.right - startX) * graphie.scale[0];
2211
+ const offsetTop =
2212
+ (polygon.top - startY) * graphie.scale[1];
2213
+ const offsetBottom =
2214
+ (startY - polygon.bottom) * graphie.scale[1];
2215
+
2216
+ $(document).bind(
2217
+ "vmousemove.polygon vmouseup.polygon",
2218
+ function (event) {
2219
+ event.preventDefault();
2220
+
2221
+ polygon.dragging = true;
2222
+ dragging = true;
2223
+
2224
+ let mouseX =
2225
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
2226
+ event.pageX -
2227
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
2228
+ $(
2229
+ graphie.raphael.canvas.parentNode,
2230
+ ).offset().left;
2231
+ let mouseY =
2232
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
2233
+ event.pageY -
2234
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
2235
+ $(
2236
+ graphie.raphael.canvas.parentNode,
2237
+ ).offset().top;
2238
+
2239
+ // no part of the polygon can go beyond 10 pixels from
2240
+ // the edge
2241
+ if (polygon.constrainToGraph) {
2242
+ mouseX = Math.max(
2243
+ offsetLeft + 10,
2244
+ Math.min(
2245
+ graphie.xpixels - 10 - offsetRight,
2246
+ mouseX,
2247
+ ),
2248
+ );
2249
+ mouseY = Math.max(
2250
+ offsetTop + 10,
2251
+ Math.min(
2252
+ graphie.ypixels - 10 - offsetBottom,
2253
+ mouseY,
2254
+ ),
2255
+ );
2256
+ }
2257
+
2258
+ let currentX =
2259
+ mouseX / graphie.scale[0] +
2260
+ graphie.range[0][0];
2261
+ let currentY =
2262
+ graphie.range[1][1] -
2263
+ mouseY / graphie.scale[1];
2264
+ if (polygon.snapX > 0) {
2265
+ currentX =
2266
+ Math.round(currentX / polygon.snapX) *
2267
+ polygon.snapX;
2268
+ }
2269
+ if (polygon.snapY > 0) {
2270
+ currentY =
2271
+ Math.round(currentY / polygon.snapY) *
2272
+ polygon.snapY;
2273
+ }
2274
+
2275
+ if (event.type === "vmousemove") {
2276
+ let dX = currentX - startX;
2277
+ let dY = currentY - startY;
2278
+
2279
+ let doMove = true;
2280
+ if (_.isFunction(polygon.onMove)) {
2281
+ const onMoveResult = polygon.onMove(
2282
+ dX,
2283
+ dY,
2284
+ );
2285
+ if (onMoveResult === false) {
2286
+ doMove = false;
2287
+ } else if (_.isArray(onMoveResult)) {
2288
+ dX = onMoveResult[0];
2289
+ dY = onMoveResult[1];
2290
+ currentX = startX + dX;
2291
+ currentY = startY + dY;
2292
+ }
2293
+ }
2294
+
2295
+ const increment = function (i: any) {
2296
+ return [
2297
+ polygonCoords[i][0] + dX,
2298
+ polygonCoords[i][1] + dY,
2299
+ ];
2300
+ };
2301
+
2302
+ if (doMove) {
2303
+ _.each(
2304
+ polygon.points,
2305
+ function (coordOrPoint, i) {
2306
+ if (isPoint(coordOrPoint)) {
2307
+ coordOrPoint.setCoord(
2308
+ increment(i),
2309
+ );
2310
+ } else {
2311
+ polygon.points[i] =
2312
+ increment(i);
2313
+ }
2314
+ },
2315
+ );
2316
+
2317
+ polygon.transform();
2318
+
2319
+ $(polygon).trigger("move");
2320
+
2321
+ lastX = currentX;
2322
+ lastY = currentY;
2323
+ }
2324
+ } else if (event.type === "vmouseup") {
2325
+ $(document).unbind(".polygon");
2326
+
2327
+ const points = _.filter(
2328
+ polygon.points,
2329
+ isPoint,
2330
+ );
2331
+ _.each(points, function (point) {
2332
+ point.dragging = false;
2333
+ });
2334
+
2335
+ polygon.dragging = false;
2336
+ dragging = false;
2337
+ if (!polygon.highlight) {
2338
+ polygon.visibleShape.animate(
2339
+ polygon.normalStyle,
2340
+ 50,
2341
+ );
2342
+
2343
+ _.each(points, function (point) {
2344
+ point.visibleShape.animate(
2345
+ point.normalStyle,
2346
+ 50,
2347
+ );
2348
+ });
2349
+ }
2350
+ if (_.isFunction(polygon.onMoveEnd)) {
2351
+ polygon.onMoveEnd(
2352
+ lastX - startX,
2353
+ lastY - startY,
2354
+ );
2355
+ }
2356
+ }
2357
+ },
2358
+ );
2359
+ }
2360
+ },
2361
+ );
2362
+ }
2363
+
2364
+ // Bring any movable points to the front
2365
+ _.invoke(_.filter(polygon.points, isPoint), "toFront");
2366
+
2367
+ return polygon;
2368
+ },
2369
+
2370
+ /**
2371
+ * Constrain a point to be within the graph (including padding).
2372
+ * If outside graph, point's x and y coordinates are clamped within
2373
+ * the graph.
2374
+ */
2375
+ constrainToBounds: function (point, padding) {
2376
+ const lower = this.unscalePoint([padding, this.ypixels - padding]);
2377
+ const upper = this.unscalePoint([this.xpixels - padding, padding]);
2378
+ const coordX = Math.max(lower[0], Math.min(upper[0], point[0]));
2379
+ const coordY = Math.max(lower[1], Math.min(upper[1], point[1]));
2380
+ return [coordX, coordY];
2381
+ },
2382
+
2383
+ /**
2384
+ * Constrain a point to be within the graph (including padding).
2385
+ * If outside graph, point is moved along the ray specified by angle
2386
+ * until inside graph.
2387
+ */
2388
+ constrainToBoundsOnAngle: function (point, padding, angle) {
2389
+ const lower = this.unscalePoint([padding, this.ypixels - padding]);
2390
+ const upper = this.unscalePoint([this.xpixels - padding, padding]);
2391
+
2392
+ let result = point.slice();
2393
+
2394
+ if (result[0] < lower[0]) {
2395
+ result = [
2396
+ lower[0],
2397
+ result[1] + (lower[0] - result[0]) * Math.tan(angle),
2398
+ ];
2399
+ } else if (result[0] > upper[0]) {
2400
+ result = [
2401
+ upper[0],
2402
+ result[1] - (result[0] - upper[0]) * Math.tan(angle),
2403
+ ];
2404
+ }
2405
+
2406
+ if (result[1] < lower[1]) {
2407
+ result = [
2408
+ result[0] + (lower[1] - result[1]) / Math.tan(angle),
2409
+ lower[1],
2410
+ ];
2411
+ } else if (result[1] > upper[1]) {
2412
+ result = [
2413
+ result[0] - (result[1] - upper[1]) / Math.tan(angle),
2414
+ upper[1],
2415
+ ];
2416
+ }
2417
+
2418
+ return result;
2419
+ },
2420
+
2421
+ // MovableAngle is an angle that can be dragged around the screen.
2422
+ // By attaching a smartPoint to the vertex and ray control points, the
2423
+ // rays can be manipulated individually.
2424
+ //
2425
+ // Use only with smartPoints; add the smartPoints first, then:
2426
+ // addMovableAngle({points: [...]});
2427
+ //
2428
+ // The rays can be controlled to snap on degrees (more useful than snapping
2429
+ // on coordinates) by setting snapDegrees to a positive integer.
2430
+ //
2431
+ // The returned object includes the following properties/methods:
2432
+ //
2433
+ // - movableAngle.points
2434
+ // The movableAngle's dynamic smartPoints.
2435
+ //
2436
+ // - movableAngle.coords
2437
+ // The movableAngle's current coordinates (generated, don't edit).
2438
+ //
2439
+ addMovableAngle: function (options) {
2440
+ return new MovableAngle(this, options);
2441
+ },
2442
+
2443
+ // center: movable point
2444
+ // radius: int
2445
+ // circ: graphie circle
2446
+ // perim: invisible mouse target for dragging/changing radius
2447
+ addCircleGraph: function (options) {
2448
+ const graphie = this;
2449
+ const circle = $.extend(
2450
+ {
2451
+ center: [0, 0],
2452
+ radius: 2,
2453
+ snapX: 0.5,
2454
+ snapY: 0.5,
2455
+ snapRadius: 0.5,
2456
+ minRadius: 1,
2457
+ centerConstraints: {},
2458
+ centerNormalStyle: {},
2459
+ centerHighlightStyle: {
2460
+ stroke: KhanColors.INTERACTING,
2461
+ fill: KhanColors.INTERACTING,
2462
+ },
2463
+ circleNormalStyle: {
2464
+ stroke: KhanColors.INTERACTIVE,
2465
+ "fill-opacity": 0,
2466
+ },
2467
+ circleHighlightStyle: {
2468
+ stroke: KhanColors.INTERACTING,
2469
+ fill: KhanColors.INTERACTING,
2470
+ "fill-opacity": 0.05,
2471
+ },
2472
+ },
2473
+ options,
2474
+ );
2475
+
2476
+ const normalColor = circle.centerConstraints.fixed
2477
+ ? KhanColors.DYNAMIC
2478
+ : KhanColors.INTERACTIVE;
2479
+ const centerNormalStyle = options ? options.centerNormalStyle : null;
2480
+ circle.centerNormalStyle = _.extend(
2481
+ {},
2482
+ {
2483
+ fill: normalColor,
2484
+ stroke: normalColor,
2485
+ },
2486
+ centerNormalStyle,
2487
+ );
2488
+
2489
+ circle.centerPoint = graphie.addMovablePoint({
2490
+ graph: graphie,
2491
+ coord: circle.center,
2492
+ normalStyle: circle.centerNormalStyle,
2493
+ snapX: circle.snapX,
2494
+ snapY: circle.snapY,
2495
+ constraints: circle.centerConstraints,
2496
+ });
2497
+ circle.circ = graphie.circle(
2498
+ circle.center,
2499
+ circle.radius,
2500
+ circle.circleNormalStyle,
2501
+ );
2502
+ circle.perim = graphie.mouselayer
2503
+ .circle(
2504
+ graphie.scalePoint(circle.center)[0],
2505
+ graphie.scalePoint(circle.center)[1],
2506
+ graphie.scaleVector(circle.radius)[0],
2507
+ )
2508
+ .attr({
2509
+ "stroke-width": 20,
2510
+ opacity: 0.002, // This is as close to 0 as MSIE will allow
2511
+ });
2512
+
2513
+ // Highlight circle circumference on center point hover
2514
+ if (!circle.centerConstraints.fixed) {
2515
+ $(circle.centerPoint.mouseTarget.getMouseTarget()).on(
2516
+ "vmouseover vmouseout",
2517
+ function (event) {
2518
+ if (
2519
+ circle.centerPoint.highlight ||
2520
+ circle.centerPoint.dragging
2521
+ ) {
2522
+ circle.circ.animate(circle.circleHighlightStyle, 50);
2523
+ } else {
2524
+ circle.circ.animate(circle.circleNormalStyle, 50);
2525
+ }
2526
+ },
2527
+ );
2528
+ }
2529
+
2530
+ circle.toFront = function () {
2531
+ circle.circ.toFront();
2532
+ circle.perim.toFront();
2533
+ circle.centerPoint.visibleShape.toFront();
2534
+ if (!circle.centerConstraints.fixed) {
2535
+ circle.centerPoint.mouseTarget.toFront();
2536
+ }
2537
+ };
2538
+
2539
+ circle.centerPoint.onMove = function (x: any, y: any) {
2540
+ circle.toFront();
2541
+ circle.circ.attr({
2542
+ cx: graphie.scalePoint(x)[0],
2543
+ cy: graphie.scalePoint(y)[1],
2544
+ });
2545
+ circle.perim.attr({
2546
+ cx: graphie.scalePoint(x)[0],
2547
+ cy: graphie.scalePoint(y)[1],
2548
+ });
2549
+ if (circle.onMove) {
2550
+ circle.onMove(x, y);
2551
+ }
2552
+ };
2553
+
2554
+ $(circle.centerPoint).on("move", function () {
2555
+ circle.center = this.coord;
2556
+ $(circle).trigger("move");
2557
+ });
2558
+
2559
+ // circle.setCenter(x, y) moves the circle to the specified
2560
+ // x, y coordinate as if the user had dragged it there.
2561
+ circle.setCenter = function (x: any, y: any) {
2562
+ circle.centerPoint.setCoord([x, y]);
2563
+ circle.centerPoint.onMove(x, y);
2564
+ circle.center = [x, y];
2565
+ };
2566
+
2567
+ // circle.setRadius(r) sets the circle's radius to the specified
2568
+ // value as if the user had dragged it there.
2569
+ circle.setRadius = function (r: any) {
2570
+ circle.radius = r;
2571
+
2572
+ circle.perim.attr({
2573
+ r: graphie.scaleVector(r)[0],
2574
+ });
2575
+ circle.circ.attr({
2576
+ rx: graphie.scaleVector(r)[0],
2577
+ ry: graphie.scaleVector(r)[1],
2578
+ });
2579
+ };
2580
+
2581
+ circle.remove = function () {
2582
+ circle.centerPoint.remove();
2583
+ circle.circ.remove();
2584
+ circle.perim.remove();
2585
+ };
2586
+
2587
+ // Define a set of axes using polar coordinates to specify
2588
+ // which resizing cursor we want to show based on where the
2589
+ // mouse position lies in relation to the circle's center.
2590
+ // The first two columns in cursorAxes refer to the minimum
2591
+ // and maximum angle values bounding a circle sector, and
2592
+ // the third column refers to the cursor name that will be
2593
+ // applied if the mouse position falls inside the given sector.
2594
+ const cursorAxes = [
2595
+ [Math.PI * -1.0, Math.PI * -0.875, "ew-resize"],
2596
+ [Math.PI * -0.875, Math.PI * -0.625, "nesw-resize"],
2597
+ [Math.PI * -0.625, Math.PI * -0.375, "ns-resize"],
2598
+ [Math.PI * -0.375, Math.PI * -0.125, "nwse-resize"],
2599
+ [Math.PI * -0.125, Math.PI * 0.125, "ew-resize"],
2600
+ [Math.PI * 0.125, Math.PI * 0.375, "nesw-resize"],
2601
+ [Math.PI * 0.375, Math.PI * 0.625, "ns-resize"],
2602
+ [Math.PI * 0.625, Math.PI * 0.875, "nwse-resize"],
2603
+ [Math.PI * 0.875, Math.PI * 1.0, "ew-resize"],
2604
+ ];
2605
+
2606
+ // When the mouse moves along the circle's perimeter, we
2607
+ // dynamically set a CSS rule to show the correct
2608
+ // bidirectional cursor so a student knows they can resize
2609
+ // our circle. To do this, we convert the x and y coordinates
2610
+ // of the mouse position into polar coordinates and use the
2611
+ // defined cursorAxes above to set our rule.
2612
+ $(circle.perim.node).on("vmousemove", (event) => {
2613
+ let [x, y] = this.getMouseCoord(event);
2614
+
2615
+ x -= circle.center[0];
2616
+ y -= circle.center[1];
2617
+
2618
+ const theta = Math.atan2(y, x);
2619
+
2620
+ cursorAxes.forEach(function (axes) {
2621
+ const [min, max, cursorName] = axes;
2622
+ if (theta >= min && theta < max) {
2623
+ $(circle.perim.node).css("cursor", cursorName);
2624
+ }
2625
+ });
2626
+ });
2627
+
2628
+ // Set a default resizing-friendly cursor to be safe.
2629
+ $(circle.perim.node).css("cursor", "nesw-resize");
2630
+
2631
+ // Prevent the page from scrolling when we grab and drag the circle on
2632
+ // a mobile device.
2633
+ circle.perim.node.addEventListener(
2634
+ "touchstart",
2635
+ function (event) {
2636
+ event.preventDefault();
2637
+ },
2638
+ {passive: false},
2639
+ );
2640
+
2641
+ $(circle.perim.node).on(
2642
+ "vmouseover vmouseout vmousedown",
2643
+ function (event) {
2644
+ if (event.type === "vmouseover") {
2645
+ circle.highlight = true;
2646
+ if (!dragging) {
2647
+ // TODO(jack): Figure out why this doesn't work
2648
+ // for circleHighlightStyle's that change
2649
+ // stroke-dasharray
2650
+ circle.circ.animate(circle.circleHighlightStyle, 50);
2651
+ circle.centerPoint.visibleShape.animate(
2652
+ circle.centerHighlightStyle,
2653
+ 50,
2654
+ );
2655
+ }
2656
+ } else if (event.type === "vmouseout") {
2657
+ circle.highlight = false;
2658
+ if (!circle.dragging && !circle.centerPoint.dragging) {
2659
+ circle.circ.animate(circle.circleNormalStyle, 50);
2660
+ circle.centerPoint.visibleShape.animate(
2661
+ circle.centerNormalStyle,
2662
+ 50,
2663
+ );
2664
+ }
2665
+ } else if (
2666
+ event.type === "vmousedown" &&
2667
+ (event.which === 1 || event.which === 0)
2668
+ ) {
2669
+ event.preventDefault();
2670
+ circle.toFront();
2671
+ const startRadius = circle.radius;
2672
+
2673
+ $(document).on("vmousemove vmouseup", function (event) {
2674
+ event.preventDefault();
2675
+ circle.dragging = true;
2676
+ dragging = true;
2677
+
2678
+ if (event.type === "vmousemove") {
2679
+ const coord = graphie.constrainToBounds(
2680
+ graphie.getMouseCoord(event),
2681
+ 10,
2682
+ );
2683
+
2684
+ let radius = GraphUtils.getDistance(
2685
+ circle.centerPoint.coord,
2686
+ coord,
2687
+ );
2688
+ radius = Math.max(
2689
+ circle.minRadius,
2690
+ Math.round(radius / circle.snapRadius) *
2691
+ circle.snapRadius,
2692
+ );
2693
+ const oldRadius = circle.radius;
2694
+ let doResize = true;
2695
+ if (circle.onResize) {
2696
+ const onResizeResult = circle.onResize(
2697
+ radius,
2698
+ oldRadius,
2699
+ );
2700
+ if (_.isNumber(onResizeResult)) {
2701
+ radius = onResizeResult;
2702
+ } else if (onResizeResult === false) {
2703
+ doResize = false;
2704
+ }
2705
+ }
2706
+ if (doResize) {
2707
+ circle.setRadius(radius);
2708
+ $(circle).trigger("move");
2709
+ }
2710
+ } else if (event.type === "vmouseup") {
2711
+ $(document).off("vmousemove vmouseup");
2712
+ circle.dragging = false;
2713
+ dragging = false;
2714
+ if (circle.onResizeEnd) {
2715
+ circle.onResizeEnd(circle.radius, startRadius);
2716
+ }
2717
+ }
2718
+ });
2719
+ }
2720
+ },
2721
+ );
2722
+
2723
+ return circle;
2724
+ },
2725
+
2726
+ addRotateHandle: (function () {
2727
+ const drawRotateHandle = function (
2728
+ graphie,
2729
+ center: any,
2730
+ radius,
2731
+ halfWidth,
2732
+ lengthAngle,
2733
+ angle,
2734
+ interacting,
2735
+ ) {
2736
+ const getRotateHandlePoint = function (
2737
+ offset: number,
2738
+ distanceFromArrowMidline,
2739
+ ) {
2740
+ const distFromRotationCenter =
2741
+ radius + distanceFromArrowMidline;
2742
+ const vec = kvector.cartFromPolarDeg(
2743
+ distFromRotationCenter,
2744
+ angle + offset,
2745
+ );
2746
+ const absolute = kvector.add(center, vec);
2747
+ const pixels = graphie.scalePoint(absolute);
2748
+ return pixels[0] + "," + pixels[1];
2749
+ };
2750
+
2751
+ const innerR = graphie.scaleVector(radius - halfWidth);
2752
+ const outerR = graphie.scaleVector(radius + halfWidth);
2753
+
2754
+ // Draw the double-headed arrow thing that shows users where to
2755
+ // click and drag to rotate
2756
+ return graphie.raphael
2757
+ .path(
2758
+ // upper arrowhead
2759
+ " M" +
2760
+ getRotateHandlePoint(lengthAngle, -halfWidth) +
2761
+ " L" +
2762
+ getRotateHandlePoint(lengthAngle, -3 * halfWidth) +
2763
+ " L" +
2764
+ getRotateHandlePoint(2 * lengthAngle, 0) +
2765
+ " L" +
2766
+ getRotateHandlePoint(lengthAngle, 3 * halfWidth) +
2767
+ " L" +
2768
+ getRotateHandlePoint(lengthAngle, halfWidth) +
2769
+ // outer arc
2770
+ " A" +
2771
+ outerR[0] +
2772
+ "," +
2773
+ outerR[1] +
2774
+ ",0,0,1," +
2775
+ getRotateHandlePoint(-lengthAngle, halfWidth) +
2776
+ // lower arrowhead
2777
+ " L" +
2778
+ getRotateHandlePoint(-lengthAngle, 3 * halfWidth) +
2779
+ " L" +
2780
+ getRotateHandlePoint(-2 * lengthAngle, 0) +
2781
+ " L" +
2782
+ getRotateHandlePoint(-lengthAngle, -3 * halfWidth) +
2783
+ " L" +
2784
+ getRotateHandlePoint(-lengthAngle, -halfWidth) +
2785
+ // inner arc
2786
+ " A" +
2787
+ innerR[0] +
2788
+ "," +
2789
+ innerR[1] +
2790
+ ",0,0,0," +
2791
+ getRotateHandlePoint(lengthAngle, -halfWidth) +
2792
+ " Z",
2793
+ )
2794
+ .attr({
2795
+ stroke: null,
2796
+ fill: interacting
2797
+ ? KhanColors.INTERACTING
2798
+ : KhanColors.INTERACTIVE,
2799
+ });
2800
+ };
2801
+
2802
+ return function (options: any) {
2803
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
2804
+ const graph = this;
2805
+
2806
+ let rotatePoint = options.center;
2807
+ const radius = options.radius;
2808
+ const lengthAngle = options.lengthAngle || 30;
2809
+ const hideArrow = options.hideArrow || false;
2810
+ const mouseTarget = options.mouseTarget;
2811
+ const id = _.uniqueId("rotateHandle");
2812
+
2813
+ // Normalize rotatePoint into something that always looks
2814
+ // like a movablePoint
2815
+ if (_.isArray(rotatePoint)) {
2816
+ rotatePoint = {
2817
+ coord: rotatePoint,
2818
+ };
2819
+ }
2820
+
2821
+ const rotateHandle = graph.addMovablePoint({
2822
+ coord: kpoint.addVector(
2823
+ rotatePoint.coord,
2824
+ kvector.cartFromPolarDeg(radius, options.angleDeg || 0),
2825
+ ),
2826
+ constraints: {
2827
+ fixedDistance: {
2828
+ dist: radius,
2829
+ point: rotatePoint,
2830
+ },
2831
+ },
2832
+ mouseTarget: mouseTarget,
2833
+ });
2834
+
2835
+ // move the rotatePoint in front of the rotateHandle to avoid
2836
+ // confusing clicking/scaling of the rotateHandle when the user
2837
+ // intends to click on the rotatePoint
2838
+ rotatePoint.toFront();
2839
+
2840
+ let rotatePointPrevCoord = rotatePoint.coord;
2841
+ let rotateHandlePrevCoord = rotateHandle.coord;
2842
+ let rotateHandleStartCoord = rotateHandlePrevCoord;
2843
+ let isRotating = false;
2844
+ let isHovering = false;
2845
+ let drawnRotateHandle;
2846
+
2847
+ const redrawRotateHandle = function (
2848
+ handleCoord: Array<never> | ReadonlyArray<number>,
2849
+ ) {
2850
+ if (hideArrow) {
2851
+ return; // Don't draw anything!
2852
+ }
2853
+
2854
+ const handleVec = kvector.subtract(
2855
+ handleCoord,
2856
+ rotatePoint.coord,
2857
+ );
2858
+ const handlePolar = kvector.polarDegFromCart(handleVec);
2859
+ const angle = handlePolar[1];
2860
+
2861
+ if (drawnRotateHandle) {
2862
+ drawnRotateHandle.remove();
2863
+ }
2864
+
2865
+ drawnRotateHandle = drawRotateHandle(
2866
+ graph,
2867
+ rotatePoint.coord,
2868
+ options.radius,
2869
+ isRotating || isHovering
2870
+ ? options.hoverWidth / 2
2871
+ : options.width / 2,
2872
+ lengthAngle,
2873
+ angle,
2874
+ isRotating || isHovering,
2875
+ );
2876
+ };
2877
+
2878
+ // when the rotation center moves, we need to move
2879
+ // the rotationHandle as well, or it will end up out
2880
+ // of sync
2881
+ $(rotatePoint).on("move." + id, function () {
2882
+ const delta = kvector.subtract(
2883
+ rotatePoint.coord,
2884
+ rotatePointPrevCoord,
2885
+ );
2886
+
2887
+ rotateHandle.setCoord(kvector.add(rotateHandle.coord, delta));
2888
+
2889
+ redrawRotateHandle(rotateHandle.coord);
2890
+
2891
+ rotatePointPrevCoord = rotatePoint.coord;
2892
+ rotateHandle.constraints.fixedDistance.point = rotatePoint;
2893
+ rotateHandlePrevCoord = rotateHandle.coord;
2894
+ });
2895
+
2896
+ // Rotate polygon with rotateHandle
2897
+ rotateHandle.onMove = function (x, y) {
2898
+ if (!isRotating) {
2899
+ rotateHandleStartCoord = rotateHandlePrevCoord;
2900
+ isRotating = true;
2901
+ }
2902
+
2903
+ let coord = [x, y];
2904
+
2905
+ if (options.onMove) {
2906
+ const oldPolar = kvector.polarDegFromCart(
2907
+ kvector.subtract(
2908
+ rotateHandlePrevCoord,
2909
+ rotatePoint.coord,
2910
+ ),
2911
+ );
2912
+ const newPolar = kvector.polarDegFromCart(
2913
+ kvector.subtract(coord, rotatePoint.coord),
2914
+ );
2915
+
2916
+ const oldAngle = oldPolar[1];
2917
+ const newAngle = newPolar[1];
2918
+ let result = options.onMove(newAngle, oldAngle);
2919
+ if (result != null && result !== true) {
2920
+ if (result === false) {
2921
+ result = oldAngle;
2922
+ }
2923
+ coord = kvector.add(
2924
+ rotatePoint.coord,
2925
+ kvector.cartFromPolarDeg(oldPolar[0], result),
2926
+ );
2927
+ }
2928
+ }
2929
+
2930
+ redrawRotateHandle(coord);
2931
+
2932
+ rotateHandlePrevCoord = coord;
2933
+ return coord;
2934
+ };
2935
+
2936
+ rotateHandle.onMoveEnd = function () {
2937
+ isRotating = false;
2938
+ redrawRotateHandle(rotateHandle.coord);
2939
+ if (options.onMoveEnd) {
2940
+ const oldPolar = kvector.polarDegFromCart(
2941
+ kvector.subtract(
2942
+ rotateHandleStartCoord,
2943
+ rotatePoint.coord,
2944
+ ),
2945
+ );
2946
+ const newPolar = kvector.polarDegFromCart(
2947
+ kvector.subtract(rotateHandle.coord, rotatePoint.coord),
2948
+ );
2949
+ options.onMoveEnd(newPolar[1], oldPolar[1]);
2950
+ }
2951
+ };
2952
+
2953
+ // Remove the default dot added by the movablePoint since we have
2954
+ // our double-arrow thing
2955
+ rotateHandle.visibleShape.remove();
2956
+
2957
+ if (!mouseTarget) {
2958
+ // Make the default mouse target bigger to encompass the whole
2959
+ // area around the double-arrow thing
2960
+ rotateHandle.mouseTarget.attr({scale: 2});
2961
+ }
2962
+
2963
+ const $mouseTarget = $(rotateHandle.mouseTarget.getMouseTarget());
2964
+ $mouseTarget.bind("vmouseover", function (e) {
2965
+ isHovering = true;
2966
+ redrawRotateHandle(rotateHandle.coord);
2967
+ });
2968
+ $mouseTarget.bind("vmouseout", function (e) {
2969
+ isHovering = false;
2970
+ redrawRotateHandle(rotateHandle.coord);
2971
+ });
2972
+
2973
+ redrawRotateHandle(rotateHandle.coord);
2974
+
2975
+ const oldRemove = rotateHandle.remove;
2976
+ rotateHandle.remove = function () {
2977
+ oldRemove.call(rotateHandle);
2978
+ if (drawnRotateHandle) {
2979
+ drawnRotateHandle.remove();
2980
+ }
2981
+ $(rotatePoint).off("move." + id);
2982
+ };
2983
+
2984
+ rotateHandle.update = function () {
2985
+ redrawRotateHandle(rotateHandle.coord);
2986
+ };
2987
+
2988
+ return rotateHandle;
2989
+ };
2990
+ })(),
2991
+
2992
+ addReflectButton: (function () {
2993
+ const drawButton = function (
2994
+ graphie,
2995
+ buttonCoord: kpoint.Point,
2996
+ lineCoords: Array<any> | Array<never> | Array<any | Array<number>>,
2997
+ size: number,
2998
+ distanceFromCenter: number,
2999
+ leftStyle: any,
3000
+ rightStyle: any,
3001
+ ) {
3002
+ // Avoid invalid lines
3003
+ if (kpoint.equal(lineCoords[0], lineCoords[1])) {
3004
+ lineCoords = [
3005
+ lineCoords[0],
3006
+ kpoint.addVector(lineCoords[0], [1, 1]),
3007
+ ];
3008
+ }
3009
+
3010
+ const lineDirection = kvector.normalize(
3011
+ kvector.subtract(lineCoords[1], lineCoords[0]),
3012
+ );
3013
+
3014
+ const lineVec = kvector.scale(lineDirection, size / 2);
3015
+
3016
+ const centerVec = kvector.scale(lineDirection, distanceFromCenter);
3017
+ const leftCenterVec = kvector.rotateDeg(centerVec, 90);
3018
+ const rightCenterVec = kvector.rotateDeg(centerVec, -90);
3019
+
3020
+ const negLineVec = kvector.negate(lineVec);
3021
+ const leftVec = kvector.rotateDeg(lineVec, 90);
3022
+ const rightVec = kvector.rotateDeg(lineVec, -90);
3023
+
3024
+ const leftCenter = kpoint.addVectors(buttonCoord, leftCenterVec);
3025
+ const rightCenter = kpoint.addVectors(buttonCoord, rightCenterVec);
3026
+
3027
+ const leftCoord1 = kpoint.addVectors(
3028
+ buttonCoord,
3029
+ leftCenterVec,
3030
+ lineVec,
3031
+ leftVec,
3032
+ );
3033
+ const leftCoord2 = kpoint.addVectors(
3034
+ buttonCoord,
3035
+ leftCenterVec,
3036
+ negLineVec,
3037
+ leftVec,
3038
+ );
3039
+ const rightCoord1 = kpoint.addVectors(
3040
+ buttonCoord,
3041
+ rightCenterVec,
3042
+ lineVec,
3043
+ rightVec,
3044
+ );
3045
+ const rightCoord2 = kpoint.addVectors(
3046
+ buttonCoord,
3047
+ rightCenterVec,
3048
+ negLineVec,
3049
+ rightVec,
3050
+ );
3051
+
3052
+ const leftButton = graphie.path(
3053
+ [leftCenter, leftCoord1, leftCoord2, true],
3054
+ leftStyle,
3055
+ );
3056
+ const rightButton = graphie.path(
3057
+ [rightCenter, rightCoord1, rightCoord2, true],
3058
+ rightStyle,
3059
+ );
3060
+
3061
+ return {
3062
+ remove: function () {
3063
+ leftButton.remove();
3064
+ rightButton.remove();
3065
+ },
3066
+ };
3067
+ };
3068
+
3069
+ return function (options: any) {
3070
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3071
+ const graphie = this;
3072
+
3073
+ const line = options.line;
3074
+
3075
+ const button = graphie.addMovablePoint({
3076
+ constraints: options.constraints,
3077
+ coord: kline.midpoint([line.pointA.coord, line.pointZ.coord]),
3078
+ snapX: graphie.snap[0],
3079
+ snapY: graphie.snap[1],
3080
+ onMove: function (x, y) {
3081
+ // Don't allow the button to actually move. This is a hack
3082
+ // around the inability to both set a point as fixed AND
3083
+ // allow it to be clicked.
3084
+ return false;
3085
+ },
3086
+ onMoveEnd: function (x, y) {
3087
+ if (options.onMoveEnd) {
3088
+ options.onMoveEnd.call(this, x, y);
3089
+ }
3090
+ },
3091
+ });
3092
+
3093
+ let isHovering = false;
3094
+ let isFlipped = false;
3095
+ let currentlyDrawnButton;
3096
+
3097
+ const isHighlight = function () {
3098
+ return isHovering;
3099
+ };
3100
+
3101
+ const styles = _.map([0, 1], function (isHighlight) {
3102
+ const baseStyle = isHighlight
3103
+ ? options.highlightStyle
3104
+ : options.normalStyle;
3105
+
3106
+ return _.map([0, 1], function (opacity) {
3107
+ return _.defaults(
3108
+ {
3109
+ "fill-opacity": opacity,
3110
+ },
3111
+ baseStyle,
3112
+ );
3113
+ });
3114
+ });
3115
+
3116
+ const getStyle = function (isRight: number | boolean) {
3117
+ if (isFlipped) {
3118
+ isRight = !isRight;
3119
+ }
3120
+ return styles[+isHighlight()][+isRight];
3121
+ };
3122
+
3123
+ const redraw = function (
3124
+ coord: kpoint.Point,
3125
+ lineCoords: Array<any> | Array<never>,
3126
+ ) {
3127
+ if (currentlyDrawnButton) {
3128
+ currentlyDrawnButton.remove();
3129
+ }
3130
+ currentlyDrawnButton = drawButton(
3131
+ graphie,
3132
+ coord,
3133
+ lineCoords,
3134
+ isHighlight() ? options.size * 1.5 : options.size,
3135
+ isHighlight() ? options.size * 0.125 : 0.25,
3136
+ getStyle(0),
3137
+ getStyle(1),
3138
+ );
3139
+ };
3140
+
3141
+ const update = function (coordA, coordZ) {
3142
+ coordA = coordA || line.pointA.coord;
3143
+ coordZ = coordZ || line.pointZ.coord;
3144
+
3145
+ const buttonCoord = kline.midpoint([coordA, coordZ]);
3146
+ button.setCoord(buttonCoord);
3147
+
3148
+ redraw(buttonCoord, [coordA, coordZ]);
3149
+ };
3150
+
3151
+ $(line).on("move", _.bind(update, button, null, null));
3152
+
3153
+ const $mouseTarget = $(button.mouseTarget.getMouseTarget());
3154
+ $mouseTarget.on("vclick", function () {
3155
+ const result = options.onClick();
3156
+ if (result !== false) {
3157
+ isFlipped = !isFlipped;
3158
+ redraw(button.coord, [
3159
+ line.pointA.coord,
3160
+ line.pointZ.coord,
3161
+ ]);
3162
+ }
3163
+ });
3164
+
3165
+ // Bring the reflection line handles in front of the button, so
3166
+ // that if we drag the reflectPoints really close together, we can
3167
+ // still move the handles away from each other, rather than only
3168
+ // being able to apply the reflection.
3169
+ line.pointA.toFront();
3170
+ line.pointZ.toFront();
3171
+
3172
+ // Replace the visual point with the double triangle thing
3173
+ button.visibleShape.remove();
3174
+ const pointScale = graphie.scaleVector(options.size)[0] / 20;
3175
+ button.mouseTarget.attr({scale: 1.5 * pointScale});
3176
+ $mouseTarget.css("cursor", "pointer");
3177
+
3178
+ // Make the arrow-thing grow and shrink with mouseover/out
3179
+ $mouseTarget.bind("vmouseover", function (e) {
3180
+ isHovering = true;
3181
+ redraw(button.coord, [line.pointA.coord, line.pointZ.coord]);
3182
+ });
3183
+ $mouseTarget.bind("vmouseout", function (e) {
3184
+ isHovering = false;
3185
+ redraw(button.coord, [line.pointA.coord, line.pointZ.coord]);
3186
+ });
3187
+
3188
+ const oldButtonRemove = button.remove;
3189
+ button.remove = function () {
3190
+ currentlyDrawnButton.remove();
3191
+ oldButtonRemove.call(button);
3192
+ };
3193
+
3194
+ button.update = update;
3195
+ button.isFlipped = function () {
3196
+ return isFlipped;
3197
+ };
3198
+
3199
+ // @ts-expect-error [FEI-5003] - TS2554 - Expected 2 arguments, but got 0.
3200
+ update();
3201
+ return button;
3202
+ };
3203
+ })(),
3204
+
3205
+ protractor: function (center) {
3206
+ // @ts-expect-error [FEI-5003] - TS2350 - Only a void function can be called with the 'new' keyword.
3207
+ return new Protractor(this, center);
3208
+ },
3209
+
3210
+ ruler: function (options) {
3211
+ // @ts-expect-error [FEI-5003] - TS2350 - Only a void function can be called with the 'new' keyword.
3212
+ return new Ruler(this, options || {});
3213
+ },
3214
+
3215
+ addPoints: addPoints,
3216
+ });
3217
+
3218
+ function Protractor(graph: any, center: any) {
3219
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3220
+ this.set = graph.raphael.set();
3221
+
3222
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3223
+ this.cx = center[0];
3224
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3225
+ this.cy = center[1];
3226
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3227
+ const pro = this;
3228
+
3229
+ const r = graph.unscaleVector(180.5)[0];
3230
+ const imgPos = graph.scalePoint([
3231
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3232
+ this.cx - r,
3233
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3234
+ this.cy + r - graph.unscaleVector(10.5)[1],
3235
+ ]);
3236
+ const image = graph.mouselayer.image(
3237
+ "https://ka-perseus-graphie.s3.amazonaws.com/e9d032f2ab8b95979f674fbfa67056442ba1ff6a.png",
3238
+ imgPos[0],
3239
+ imgPos[1],
3240
+ 360,
3241
+ 180,
3242
+ );
3243
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3244
+ this.set.push(image);
3245
+
3246
+ // Prevent the page from scrolling when we grab and drag the image on a
3247
+ // mobile device.
3248
+ image.node.addEventListener(
3249
+ "touchstart",
3250
+ function (event) {
3251
+ event.preventDefault();
3252
+ },
3253
+ {passive: false},
3254
+ );
3255
+
3256
+ const arrowHelper = function (angle: number, pixelsFromEdge: number) {
3257
+ const scaledRadius = graph.scaleVector(r);
3258
+ scaledRadius[0] -= 16;
3259
+ scaledRadius[1] -= 16;
3260
+ const scaledCenter = graph.scalePoint(center);
3261
+ const x =
3262
+ Math.sin(((angle + 90) * Math.PI) / 180) *
3263
+ (scaledRadius[0] + pixelsFromEdge) +
3264
+ scaledCenter[0];
3265
+ const y =
3266
+ Math.cos(((angle + 90) * Math.PI) / 180) *
3267
+ (scaledRadius[1] + pixelsFromEdge) +
3268
+ scaledCenter[1];
3269
+ return x + "," + y;
3270
+ };
3271
+
3272
+ const arrow = graph.raphael
3273
+ .path(
3274
+ " M" +
3275
+ arrowHelper(180, 6) +
3276
+ " L" +
3277
+ arrowHelper(180, 2) +
3278
+ " L" +
3279
+ arrowHelper(183, 10) +
3280
+ " L" +
3281
+ arrowHelper(180, 18) +
3282
+ " L" +
3283
+ arrowHelper(180, 14) +
3284
+ " A" +
3285
+ (graph.scaleVector(r)[0] + 10) +
3286
+ "," +
3287
+ (graph.scaleVector(r)[1] + 10) +
3288
+ ",0,0,1," +
3289
+ arrowHelper(170, 14) +
3290
+ " L" +
3291
+ arrowHelper(170, 18) +
3292
+ " L" +
3293
+ arrowHelper(167, 10) +
3294
+ " L" +
3295
+ arrowHelper(170, 2) +
3296
+ " L" +
3297
+ arrowHelper(170, 6) +
3298
+ " A" +
3299
+ (graph.scaleVector(r)[0] + 10) +
3300
+ "," +
3301
+ (graph.scaleVector(r)[1] + 10) +
3302
+ ",0,0,0," +
3303
+ arrowHelper(180, 6) +
3304
+ " Z",
3305
+ )
3306
+ .attr({
3307
+ stroke: null,
3308
+ fill: KhanColors.INTERACTIVE,
3309
+ });
3310
+
3311
+ // add it to the set so it translates with everything else
3312
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3313
+ this.set.push(arrow);
3314
+
3315
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3316
+ this.centerPoint = graph.addMovablePoint({
3317
+ coord: center,
3318
+ visible: false,
3319
+ });
3320
+
3321
+ // Use a movablePoint for rotation
3322
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3323
+ this.rotateHandle = graph.addMovablePoint({
3324
+ bounded: false,
3325
+ coord: [
3326
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3327
+ Math.sin((275 * Math.PI) / 180) * r + this.cx,
3328
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3329
+ Math.cos((275 * Math.PI) / 180) * r + this.cy,
3330
+ ],
3331
+ onMove: function (x, y) {
3332
+ const angle =
3333
+ (Math.atan2(
3334
+ pro.centerPoint.coord[1] - y,
3335
+ pro.centerPoint.coord[0] - x,
3336
+ ) *
3337
+ 180) /
3338
+ Math.PI;
3339
+ pro.rotate(-angle - 5, true);
3340
+ },
3341
+ });
3342
+
3343
+ // Add a constraint so the point moves in a circle
3344
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3345
+ this.rotateHandle.constraints.fixedDistance.dist = r;
3346
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3347
+ this.rotateHandle.constraints.fixedDistance.point = this.centerPoint;
3348
+
3349
+ // Remove the default dot added by the movablePoint since we have our
3350
+ // double-arrow thing
3351
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3352
+ this.rotateHandle.visibleShape.remove();
3353
+ // Make the mouse target bigger to encompass the whole area around the
3354
+ // double-arrow thing
3355
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3356
+ this.rotateHandle.mouseTarget.attr({scale: 2.0});
3357
+
3358
+ let isDragging = false;
3359
+ let isHovering = false;
3360
+ const isHighlight = function () {
3361
+ return isHovering || isDragging;
3362
+ };
3363
+
3364
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3365
+ const self = this;
3366
+ const $mouseTarget = $(self.rotateHandle.mouseTarget.getMouseTarget());
3367
+ $mouseTarget.bind("vmousedown", function (event) {
3368
+ isDragging = true;
3369
+ $mouseTarget.css("cursor", "-webkit-grabbing");
3370
+ $mouseTarget.css("cursor", "grabbing");
3371
+ arrow.animate({scale: 1.5, fill: KhanColors.INTERACTING}, 50);
3372
+
3373
+ $(document).bind("vmouseup.rotateHandle", function (event) {
3374
+ isDragging = false;
3375
+ $mouseTarget.css("cursor", "-webkit-grab");
3376
+ $mouseTarget.css("cursor", "grab");
3377
+
3378
+ if (!isHighlight()) {
3379
+ arrow.animate({scale: 1.0, fill: KhanColors.INTERACTIVE}, 50);
3380
+ }
3381
+
3382
+ $(document).unbind("vmouseup.rotateHandle");
3383
+ });
3384
+ });
3385
+
3386
+ $mouseTarget.bind("vmouseover", function (event) {
3387
+ isHovering = true;
3388
+ arrow.animate({scale: 1.5, fill: KhanColors.INTERACTING}, 50);
3389
+ });
3390
+ $mouseTarget.bind("vmouseout", function (event) {
3391
+ isHovering = false;
3392
+ if (!isHighlight()) {
3393
+ arrow.animate({scale: 1.0, fill: KhanColors.INTERACTIVE}, 50);
3394
+ }
3395
+ });
3396
+
3397
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3398
+ const setNodes = $.map(this.set, function (el) {
3399
+ return el.node;
3400
+ });
3401
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3402
+ this.makeTranslatable = function makeTranslatable() {
3403
+ $(setNodes).css("cursor", "move");
3404
+ $mouseTarget.css("cursor", "-webkit-grab");
3405
+ $mouseTarget.css("cursor", "grab");
3406
+
3407
+ $(setNodes).bind("vmousedown", function (event) {
3408
+ event.preventDefault();
3409
+ let startx =
3410
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
3411
+ event.pageX - $(graph.raphael.canvas.parentNode).offset().left;
3412
+ let starty =
3413
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
3414
+ event.pageY - $(graph.raphael.canvas.parentNode).offset().top;
3415
+
3416
+ $(document).bind("vmousemove.protractor", function (event) {
3417
+ let mouseX =
3418
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
3419
+ event.pageX -
3420
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
3421
+ $(graph.raphael.canvas.parentNode).offset().left;
3422
+ let mouseY =
3423
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
3424
+ event.pageY -
3425
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
3426
+ $(graph.raphael.canvas.parentNode).offset().top;
3427
+ // can't go beyond 10 pixels from the edge
3428
+ mouseX = Math.max(10, Math.min(graph.xpixels - 10, mouseX));
3429
+ mouseY = Math.max(10, Math.min(graph.ypixels - 10, mouseY));
3430
+
3431
+ const dx = mouseX - startx;
3432
+ const dy = mouseY - starty;
3433
+
3434
+ $.each(pro.set.items, function () {
3435
+ this.translate(dx, dy);
3436
+ });
3437
+ pro.centerPoint.setCoord([
3438
+ pro.centerPoint.coord[0] + dx / graph.scale[0],
3439
+ pro.centerPoint.coord[1] - dy / graph.scale[1],
3440
+ ]);
3441
+ pro.rotateHandle.setCoord([
3442
+ pro.rotateHandle.coord[0] + dx / graph.scale[0],
3443
+ pro.rotateHandle.coord[1] - dy / graph.scale[1],
3444
+ ]);
3445
+ startx = mouseX;
3446
+ starty = mouseY;
3447
+ });
3448
+
3449
+ $(document).one("vmouseup", function (event) {
3450
+ $(document).unbind("vmousemove.protractor");
3451
+ });
3452
+ });
3453
+ };
3454
+
3455
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3456
+ this.rotation = 0;
3457
+
3458
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3459
+ this.rotate = function (offset: any, absolute: any) {
3460
+ const center = graph.scalePoint(this.centerPoint.coord);
3461
+
3462
+ if (absolute) {
3463
+ this.rotation = 0;
3464
+ }
3465
+
3466
+ this.set.rotate(this.rotation + offset, center[0], center[1]);
3467
+ this.rotation = this.rotation + offset;
3468
+
3469
+ return this;
3470
+ };
3471
+
3472
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3473
+ this.moveTo = function moveTo(x: any, y: any) {
3474
+ const start = graph.scalePoint(pro.centerPoint.coord);
3475
+ const end = graph.scalePoint([x, y]);
3476
+ const time = GraphUtils.getDistance(start, end) * 2;
3477
+
3478
+ $({x: start[0], y: start[1]}).animate(
3479
+ {x: end[0], y: end[1]},
3480
+ {
3481
+ duration: time,
3482
+ step: function (now, fx) {
3483
+ let dx = 0;
3484
+ let dy = 0;
3485
+ if (fx.prop === "x") {
3486
+ dx = now - graph.scalePoint(pro.centerPoint.coord)[0];
3487
+ } else if (fx.prop === "y") {
3488
+ dy = now - graph.scalePoint(pro.centerPoint.coord)[1];
3489
+ }
3490
+ $.each(pro.set.items, function () {
3491
+ this.translate(dx, dy);
3492
+ });
3493
+ pro.centerPoint.setCoord([
3494
+ pro.centerPoint.coord[0] + dx / graph.scale[0],
3495
+ pro.centerPoint.coord[1] - dy / graph.scale[1],
3496
+ ]);
3497
+ pro.rotateHandle.setCoord([
3498
+ pro.rotateHandle.coord[0] + dx / graph.scale[0],
3499
+ pro.rotateHandle.coord[1] - dy / graph.scale[1],
3500
+ ]);
3501
+ },
3502
+ },
3503
+ );
3504
+ };
3505
+
3506
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3507
+ this.rotateTo = function rotateTo(angle: any) {
3508
+ if (Math.abs(this.rotation - angle) > 180) {
3509
+ this.rotation += 360;
3510
+ }
3511
+ const time = Math.abs(this.rotation - angle) * 5;
3512
+ $({0: this.rotation}).animate(
3513
+ // $FlowFixMe[unsupported-syntax]
3514
+ {0: angle},
3515
+ {
3516
+ duration: time,
3517
+ step: function (now, fx) {
3518
+ pro.rotate(now, true);
3519
+ pro.rotateHandle.setCoord([
3520
+ Math.sin(((now + 275) * Math.PI) / 180) * r +
3521
+ pro.centerPoint.coord[0],
3522
+ Math.cos(((now + 275) * Math.PI) / 180) * r +
3523
+ pro.centerPoint.coord[1],
3524
+ ]);
3525
+ },
3526
+ },
3527
+ );
3528
+ };
3529
+
3530
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3531
+ this.remove = function () {
3532
+ this.set.remove();
3533
+ };
3534
+
3535
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3536
+ this.makeTranslatable();
3537
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3538
+ return this;
3539
+ }
3540
+
3541
+ function Ruler(graphie: any, options: any) {
3542
+ _.defaults(options, {
3543
+ center: [0, 0],
3544
+ pixelsPerUnit: 40,
3545
+ ticksPerUnit: 10, // 10 or power of 2
3546
+ units: 10, // the length the ruler can measure
3547
+ label: "", // e.g "cm" (the shorter, the better)
3548
+ style: {
3549
+ fill: null,
3550
+ stroke: KhanColors.GRAY,
3551
+ },
3552
+ });
3553
+
3554
+ const light = _.extend({}, options.style, {strokeWidth: 1});
3555
+ const bold = _.extend({}, options.style, {strokeWidth: 2});
3556
+
3557
+ const width = options.units * options.pixelsPerUnit;
3558
+ const height = 50;
3559
+
3560
+ const leftBottom = graphie.unscalePoint(
3561
+ kvector.subtract(
3562
+ graphie.scalePoint(options.center),
3563
+ kvector.scale([width, -height], 0.5),
3564
+ ),
3565
+ );
3566
+
3567
+ const graphieUnitsPerUnit = options.pixelsPerUnit / graphie.scale[0];
3568
+ const graphieUnitsHeight = height / graphie.scale[0];
3569
+
3570
+ const rightTop = kvector.add(leftBottom, [
3571
+ options.units * graphieUnitsPerUnit,
3572
+ graphieUnitsHeight,
3573
+ ]);
3574
+
3575
+ const tickHeight = 1.0;
3576
+ let tickHeightMap;
3577
+
3578
+ if (options.ticksPerUnit === 10) {
3579
+ // decimal, as on a centimeter ruler
3580
+ tickHeightMap = {
3581
+ // $FlowFixMe[unsupported-syntax]
3582
+ 10: tickHeight,
3583
+ // $FlowFixMe[unsupported-syntax]
3584
+ 5: tickHeight * 0.55,
3585
+ // $FlowFixMe[unsupported-syntax]
3586
+ 1: tickHeight * 0.35,
3587
+ };
3588
+ } else {
3589
+ const sizes = [1, 0.6, 0.45, 0.3];
3590
+
3591
+ tickHeightMap = {};
3592
+ for (let i = options.ticksPerUnit; i >= 1; i /= 2) {
3593
+ tickHeightMap[i] = tickHeight * (sizes.shift() || 0.2);
3594
+ }
3595
+ }
3596
+
3597
+ const tickFrequencies = _.keys(tickHeightMap).sort(function (a, b) {
3598
+ // @ts-expect-error [FEI-5003] - TS2362 - The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type. | TS2363 - The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
3599
+ return b - a;
3600
+ });
3601
+
3602
+ function getTickHeight(i: any): number {
3603
+ for (let k = 0; k < tickFrequencies.length; k++) {
3604
+ const key = tickFrequencies[k];
3605
+ // @ts-expect-error [FEI-5003] - TS2363 - The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
3606
+ if (i % key === 0) {
3607
+ return tickHeightMap[key];
3608
+ }
3609
+ }
3610
+ return 0;
3611
+ }
3612
+
3613
+ const left = leftBottom[0];
3614
+ const bottom = leftBottom[1];
3615
+ const right = rightTop[0];
3616
+ const top = rightTop[1];
3617
+
3618
+ const numTicks = options.units * options.ticksPerUnit + 1;
3619
+
3620
+ const set = graphie.raphael.set();
3621
+
3622
+ const px = 1 / graphie.scale[0];
3623
+ set.push(graphie.line([left - px, bottom], [right + px, bottom], bold));
3624
+ set.push(graphie.line([left - px, top], [right + px, top], bold));
3625
+
3626
+ _.times(numTicks, function (i) {
3627
+ const n = i / options.ticksPerUnit;
3628
+ const x = left + n * graphieUnitsPerUnit;
3629
+ const height = getTickHeight(i) * graphieUnitsHeight;
3630
+
3631
+ const style = i === 0 || i === numTicks - 1 ? bold : light;
3632
+ set.push(graphie.line([x, bottom], [x, bottom + height], style));
3633
+
3634
+ if (n % 1 === 0) {
3635
+ const coord = graphie.scalePoint([x, top]);
3636
+ let text;
3637
+ let offset;
3638
+
3639
+ if (n === 0) {
3640
+ // Unit label
3641
+ text = options.label;
3642
+ offset =
3643
+ {
3644
+ mm: 13,
3645
+ cm: 11,
3646
+ m: 8,
3647
+ km: 11,
3648
+ in: 8,
3649
+ ft: 8,
3650
+ yd: 10,
3651
+ mi: 10,
3652
+ }[text] || 3 * text.toString().length;
3653
+ } else {
3654
+ // Tick label
3655
+ text = n;
3656
+ offset = -3 * (n.toString().length + 1);
3657
+ }
3658
+ const label = graphie.raphael.text(
3659
+ coord[0] + offset,
3660
+ coord[1] + 10,
3661
+ text,
3662
+ );
3663
+ label.attr({
3664
+ "font-family": "KaTeX_Main",
3665
+ "font-size": "12px",
3666
+ color: "#444",
3667
+ });
3668
+ set.push(label);
3669
+ }
3670
+ });
3671
+
3672
+ const mouseTarget = graphie.mouselayer.path(
3673
+ GraphUtils.svgPath([
3674
+ leftBottom,
3675
+ [left, top],
3676
+ rightTop,
3677
+ [right, bottom],
3678
+ /* closed */ true,
3679
+ ]),
3680
+ );
3681
+ mouseTarget.attr({
3682
+ fill: "#000",
3683
+ opacity: 0,
3684
+ stroke: "#000",
3685
+ "stroke-width": 2,
3686
+ });
3687
+ set.push(mouseTarget);
3688
+
3689
+ // Prevent the page from scrolling when we grab and drag the ruler on a
3690
+ // mobile device.
3691
+ mouseTarget.node.addEventListener(
3692
+ "touchstart",
3693
+ function (event) {
3694
+ event.preventDefault();
3695
+ },
3696
+ {passive: false},
3697
+ );
3698
+
3699
+ const setNodes = $.map(set, function (el) {
3700
+ return el.node;
3701
+ });
3702
+ $(setNodes).css("cursor", "move");
3703
+
3704
+ $(setNodes).bind("vmousedown", function (event) {
3705
+ event.preventDefault();
3706
+ let startx =
3707
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
3708
+ event.pageX - $(graphie.raphael.canvas.parentNode).offset().left;
3709
+ let starty =
3710
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
3711
+ event.pageY - $(graphie.raphael.canvas.parentNode).offset().top;
3712
+
3713
+ $(document).bind("vmousemove.ruler", function (event) {
3714
+ let mouseX =
3715
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
3716
+ event.pageX -
3717
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'.
3718
+ $(graphie.raphael.canvas.parentNode).offset().left;
3719
+ let mouseY =
3720
+ // @ts-expect-error [FEI-5003] - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
3721
+ event.pageY - $(graphie.raphael.canvas.parentNode).offset().top;
3722
+ // can't go beyond 10 pixels from the edge
3723
+ mouseX = Math.max(10, Math.min(graphie.xpixels - 10, mouseX));
3724
+ mouseY = Math.max(10, Math.min(graphie.ypixels - 10, mouseY));
3725
+
3726
+ const dx = mouseX - startx;
3727
+ const dy = mouseY - starty;
3728
+
3729
+ set.translate(dx, dy);
3730
+ leftBottomHandle.setCoord([
3731
+ leftBottomHandle.coord[0] + dx / graphie.scale[0],
3732
+ leftBottomHandle.coord[1] - dy / graphie.scale[1],
3733
+ ]);
3734
+ rightBottomHandle.setCoord([
3735
+ rightBottomHandle.coord[0] + dx / graphie.scale[0],
3736
+ rightBottomHandle.coord[1] - dy / graphie.scale[1],
3737
+ ]);
3738
+
3739
+ startx = mouseX;
3740
+ starty = mouseY;
3741
+ });
3742
+
3743
+ $(document).one("vmouseup", function (event) {
3744
+ $(document).unbind("vmousemove.ruler");
3745
+ });
3746
+ });
3747
+
3748
+ const leftBottomHandle = graphie.addMovablePoint({
3749
+ coord: leftBottom,
3750
+ normalStyle: {
3751
+ fill: KhanColors.INTERACTIVE,
3752
+ "fill-opacity": 0,
3753
+ stroke: KhanColors.INTERACTIVE,
3754
+ },
3755
+ highlightStyle: {
3756
+ fill: KhanColors.INTERACTING,
3757
+ "fill-opacity": 0.1,
3758
+ stroke: KhanColors.INTERACTING,
3759
+ },
3760
+ pointSize: 6, // or 8 maybe?
3761
+ onMove: function (x, y) {
3762
+ const dy = rightBottomHandle.coord[1] - y;
3763
+ const dx = rightBottomHandle.coord[0] - x;
3764
+ const angle = (Math.atan2(dy, dx) * 180) / Math.PI;
3765
+ const center = kvector.scale(
3766
+ kvector.add([x, y], rightBottomHandle.coord),
3767
+ 0.5,
3768
+ );
3769
+ const scaledCenter = graphie.scalePoint(center);
3770
+ const oldCenter = kvector.scale(
3771
+ kvector.add(leftBottomHandle.coord, rightBottomHandle.coord),
3772
+ 0.5,
3773
+ );
3774
+ const scaledOldCenter = graphie.scalePoint(oldCenter);
3775
+ const diff = kvector.subtract(scaledCenter, scaledOldCenter);
3776
+ set.rotate(-angle, scaledOldCenter[0], scaledOldCenter[1]);
3777
+ set.translate(diff[0], diff[1]);
3778
+ },
3779
+ });
3780
+ const rightBottomHandle = graphie.addMovablePoint({
3781
+ coord: [right, bottom],
3782
+ normalStyle: {
3783
+ fill: KhanColors.INTERACTIVE,
3784
+ "fill-opacity": 0,
3785
+ stroke: KhanColors.INTERACTIVE,
3786
+ },
3787
+ highlightStyle: {
3788
+ fill: KhanColors.INTERACTING,
3789
+ "fill-opacity": 0.1,
3790
+ stroke: KhanColors.INTERACTING,
3791
+ },
3792
+ pointSize: 6, // or 8 maybe?
3793
+ onMove: function (x, y) {
3794
+ const dy = y - leftBottomHandle.coord[1];
3795
+ const dx = x - leftBottomHandle.coord[0];
3796
+ const angle = (Math.atan2(dy, dx) * 180) / Math.PI;
3797
+ const center = kvector.scale(
3798
+ kvector.add([x, y], leftBottomHandle.coord),
3799
+ 0.5,
3800
+ );
3801
+ const scaledCenter = graphie.scalePoint(center);
3802
+ const oldCenter = kvector.scale(
3803
+ kvector.add(leftBottomHandle.coord, rightBottomHandle.coord),
3804
+ 0.5,
3805
+ );
3806
+ const scaledOldCenter = graphie.scalePoint(oldCenter);
3807
+ const diff = kvector.subtract(scaledCenter, scaledOldCenter);
3808
+ set.rotate(-angle, scaledOldCenter[0], scaledOldCenter[1]);
3809
+ set.translate(diff[0], diff[1]);
3810
+ },
3811
+ });
3812
+
3813
+ // Make each handle rotate the ruler about the other one
3814
+ leftBottomHandle.constraints.fixedDistance.dist = width / graphie.scale[0];
3815
+ leftBottomHandle.constraints.fixedDistance.point = rightBottomHandle;
3816
+ rightBottomHandle.constraints.fixedDistance.dist = width / graphie.scale[0];
3817
+ rightBottomHandle.constraints.fixedDistance.point = leftBottomHandle;
3818
+
3819
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3820
+ this.remove = function () {
3821
+ set.remove();
3822
+ leftBottomHandle.remove();
3823
+ rightBottomHandle.remove();
3824
+ };
3825
+
3826
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3827
+ return this;
3828
+ }
3829
+
3830
+ function MovableAngle(graphie: any, options: any) {
3831
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3832
+ this.graphie = graphie;
3833
+
3834
+ // TODO(alex): Move standard colors from math.js to somewhere else
3835
+ // so that they are available when this file is first parsed
3836
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3837
+ _.extend(this, options);
3838
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3839
+ _.defaults(this, {
3840
+ normalStyle: {
3841
+ stroke: KhanColors.INTERACTIVE,
3842
+ "stroke-width": 2,
3843
+ fill: KhanColors.INTERACTIVE,
3844
+ },
3845
+ highlightStyle: {
3846
+ stroke: KhanColors.INTERACTING,
3847
+ "stroke-width": 2,
3848
+ fill: KhanColors.INTERACTING,
3849
+ },
3850
+ labelStyle: {
3851
+ stroke: KhanColors.DYNAMIC,
3852
+ "stroke-width": 1,
3853
+ color: KhanColors.DYNAMIC,
3854
+ },
3855
+ angleStyle: {
3856
+ stroke: KhanColors.DYNAMIC,
3857
+ "stroke-width": 1,
3858
+ color: KhanColors.DYNAMIC,
3859
+ },
3860
+ allowReflex: true, // not on MovableAngle.prototype so that
3861
+ // it is not overridden by undefined
3862
+ });
3863
+
3864
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3865
+ if (!this.points || this.points.length !== 3) {
3866
+ throw new PerseusError(
3867
+ "MovableAngle requires 3 points",
3868
+ Errors.InvalidInput,
3869
+ );
3870
+ }
3871
+
3872
+ // Handle coordinates that are not MovablePoints (i.e. [2, 4])
3873
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3874
+ this.points = _.map(
3875
+ options.points,
3876
+ function (point) {
3877
+ if (_.isArray(point)) {
3878
+ return graphie.addMovablePoint({
3879
+ coord: point,
3880
+ visible: false,
3881
+ constraints: {
3882
+ fixed: true,
3883
+ },
3884
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3885
+ normalStyle: this.normalStyle,
3886
+ });
3887
+ }
3888
+ return point;
3889
+ },
3890
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3891
+ this,
3892
+ );
3893
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3894
+ this.coords = _.pluck(this.points, "coord");
3895
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3896
+ if (this.reflex == null) {
3897
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3898
+ if (this.allowReflex) {
3899
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3900
+ this.reflex = this._getClockwiseAngle(this.coords) > 180;
3901
+ } else {
3902
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3903
+ this.reflex = false;
3904
+ }
3905
+ }
3906
+
3907
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3908
+ this.rays = _.map(
3909
+ [0, 2],
3910
+ function (i) {
3911
+ return graphie.addMovableLineSegment({
3912
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3913
+ pointA: this.points[1],
3914
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3915
+ pointZ: this.points[i],
3916
+ fixed: true,
3917
+ extendRay: true,
3918
+ });
3919
+ },
3920
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3921
+ this,
3922
+ );
3923
+
3924
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3925
+ this.temp = [];
3926
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3927
+ this.labeledAngle = graphie.label([0, 0], "", "center", this.labelStyle);
3928
+
3929
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3930
+ if (!this.fixed) {
3931
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3932
+ this.addMoveHandlers();
3933
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3934
+ this.addHighlightHandlers();
3935
+ }
3936
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3937
+ this.update();
3938
+ }
3939
+
3940
+ _.extend(MovableAngle.prototype, {
3941
+ points: [],
3942
+ snapDegrees: 0,
3943
+ snapOffsetDeg: 0,
3944
+ angleLabel: "",
3945
+ numArcs: 1,
3946
+ pushOut: 0,
3947
+ fixed: false,
3948
+
3949
+ addMoveHandlers: function () {
3950
+ const graphie = this.graphie;
3951
+
3952
+ function tooClose(point1: Array<never>, point2: Array<never>) {
3953
+ const safeDistance = 30;
3954
+ const distance = GraphUtils.getDistance(
3955
+ graphie.scalePoint(point1),
3956
+ graphie.scalePoint(point2),
3957
+ );
3958
+ return distance < safeDistance;
3959
+ }
3960
+
3961
+ const points = this.points;
3962
+
3963
+ // Drag the vertex to move the entire angle
3964
+ points[1].onMove = function (x, y: any) {
3965
+ const oldVertex = points[1].coord;
3966
+ const newVertex = [x, y];
3967
+ const delta = addPoints(newVertex, reverseVector(oldVertex));
3968
+
3969
+ let valid = true;
3970
+ const newPoints: Record<string, any> = {};
3971
+ _.each([0, 2], function (i) {
3972
+ const oldPoint = points[i].coord;
3973
+ let newPoint = addPoints(oldPoint, delta);
3974
+
3975
+ let angle = GraphUtils.findAngle(newVertex, newPoint);
3976
+ angle *= Math.PI / 180;
3977
+ newPoint = graphie.constrainToBoundsOnAngle(
3978
+ newPoint,
3979
+ 10,
3980
+ angle,
3981
+ );
3982
+ newPoints[i] = newPoint;
3983
+
3984
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'any[]' is not assignable to parameter of type 'never[]'.
3985
+ if (tooClose(newVertex, newPoint)) {
3986
+ valid = false;
3987
+ }
3988
+ });
3989
+
3990
+ // Only move points if all new positions are valid
3991
+ if (valid) {
3992
+ _.each(newPoints, function (newPoint, i) {
3993
+ points[i].setCoord(newPoint);
3994
+ });
3995
+ }
3996
+ return valid;
3997
+ };
3998
+
3999
+ const snap = this.snapDegrees;
4000
+ const snapOffset = this.snapOffsetDeg;
4001
+
4002
+ // Drag ray control points to move each ray individually
4003
+ _.each([0, 2], function (i) {
4004
+ points[i].onMove = function (x: any, y) {
4005
+ const newPoint = [x, y];
4006
+ const vertex = points[1].coord;
4007
+
4008
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'any[]' is not assignable to parameter of type 'never[]'.
4009
+ if (tooClose(vertex, newPoint)) {
4010
+ return false;
4011
+ }
4012
+ if (snap) {
4013
+ let angle = GraphUtils.findAngle(newPoint, vertex);
4014
+ angle =
4015
+ Math.round((angle - snapOffset) / snap) * snap +
4016
+ snapOffset;
4017
+ const distance = GraphUtils.getDistance(newPoint, vertex);
4018
+ return addPoints(vertex, graphie.polar(distance, angle));
4019
+ }
4020
+ return true;
4021
+ };
4022
+ });
4023
+
4024
+ // Expose only a single move event
4025
+ $(points).on(
4026
+ "move",
4027
+ function () {
4028
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4029
+ this.update();
4030
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4031
+ $(this).trigger("move");
4032
+ }.bind(this),
4033
+ );
4034
+ },
4035
+
4036
+ addHighlightHandlers: function () {
4037
+ const vertex = this.points[1];
4038
+
4039
+ vertex.onHighlight = function () {
4040
+ _.each(
4041
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4042
+ this.points,
4043
+ function (point) {
4044
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4045
+ point.visibleShape.animate(this.highlightStyle, 50);
4046
+ },
4047
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4048
+ this,
4049
+ );
4050
+ _.each(
4051
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4052
+ this.rays,
4053
+ function (ray) {
4054
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4055
+ ray.visibleLine.animate(this.highlightStyle, 50);
4056
+ ray.arrowStyle = _.extend({}, ray.arrowStyle, {
4057
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4058
+ color: this.highlightStyle.stroke,
4059
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4060
+ stroke: this.highlightStyle.stroke,
4061
+ });
4062
+ },
4063
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4064
+ this,
4065
+ );
4066
+
4067
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4068
+ this.angleStyle = _.extend({}, this.angleStyle, {
4069
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4070
+ color: this.highlightStyle.stroke,
4071
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4072
+ stroke: this.highlightStyle.stroke,
4073
+ });
4074
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4075
+ this.update();
4076
+ }.bind(this);
4077
+
4078
+ vertex.onUnhighlight = function () {
4079
+ _.each(
4080
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4081
+ this.points,
4082
+ function (point) {
4083
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4084
+ point.visibleShape.animate(this.normalStyle, 50);
4085
+ },
4086
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4087
+ this,
4088
+ );
4089
+ _.each(
4090
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4091
+ this.rays,
4092
+ function (ray) {
4093
+ ray.visibleLine.animate(ray.normalStyle, 50);
4094
+ ray.arrowStyle = _.extend({}, ray.arrowStyle, {
4095
+ color: ray.normalStyle.stroke,
4096
+ stroke: ray.normalStyle.stroke,
4097
+ });
4098
+ },
4099
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4100
+ this,
4101
+ );
4102
+
4103
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4104
+ this.angleStyle = _.extend({}, this.angleStyle, {
4105
+ color: KhanColors.DYNAMIC,
4106
+ stroke: KhanColors.DYNAMIC,
4107
+ });
4108
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4109
+ this.update();
4110
+ }.bind(this);
4111
+ },
4112
+
4113
+ /**
4114
+ * Returns the angle in [0, 360) degrees created by the
4115
+ * coords when interpreted in a clockwise direction.
4116
+ */
4117
+ _getClockwiseAngle: function (coords) {
4118
+ const rawAngle = GraphUtils.findAngle(
4119
+ // The order of these is "weird" to match what a clockwise
4120
+ // order is in graphie.labelAngle
4121
+ coords[2], // from the second point
4122
+ coords[0], // clockwise to the first point
4123
+ coords[1], // the vertex parameter is last
4124
+ );
4125
+ const clockwiseAngle = rawAngle + (360 % 360);
4126
+
4127
+ return clockwiseAngle;
4128
+ },
4129
+
4130
+ isReflex: function () {
4131
+ return this.reflex;
4132
+ },
4133
+
4134
+ isClockwise: function () {
4135
+ const clockwiseReflexive = this._getClockwiseAngle(this.coords) > 180;
4136
+ return clockwiseReflexive === this.reflex;
4137
+ },
4138
+
4139
+ getClockwiseCoords: function () {
4140
+ if (this.isClockwise()) {
4141
+ return _.clone(this.coords);
4142
+ }
4143
+ return _.clone(this.coords).reverse();
4144
+ },
4145
+
4146
+ update: function (shouldChangeReflexivity) {
4147
+ const prevCoords = this.coords;
4148
+ this.coords = _.pluck(this.points, "coord");
4149
+
4150
+ // Update lines
4151
+ _.invoke(this.points, "updateLineEnds");
4152
+
4153
+ const prevAngle = this._getClockwiseAngle(prevCoords);
4154
+ const angle = this._getClockwiseAngle(this.coords);
4155
+ const prevClockwiseReflexive = prevAngle > 180;
4156
+ const clockwiseReflexive = angle > 180;
4157
+
4158
+ if (this.allowReflex) {
4159
+ if (shouldChangeReflexivity == null) {
4160
+ shouldChangeReflexivity =
4161
+ prevClockwiseReflexive !== clockwiseReflexive &&
4162
+ Math.abs(angle - prevAngle) < 180;
4163
+ }
4164
+
4165
+ if (shouldChangeReflexivity) {
4166
+ this.reflex = !this.reflex;
4167
+ }
4168
+ }
4169
+
4170
+ _.invoke(this.temp, "remove");
4171
+ this.temp = this.graphie.labelAngle({
4172
+ point1: this.coords[0],
4173
+ vertex: this.coords[1],
4174
+ point3: this.coords[2],
4175
+ label: this.labeledAngle,
4176
+ text: this.angleLabel,
4177
+ numArcs: this.numArcs,
4178
+ pushOut: this.pushOut,
4179
+ clockwise: this.reflex === clockwiseReflexive,
4180
+ style: this.angleStyle,
4181
+ });
4182
+ },
4183
+
4184
+ remove: function () {
4185
+ _.invoke(this.rays, "remove");
4186
+ _.invoke(this.temp, "remove");
4187
+ this.labeledAngle.remove();
4188
+ },
4189
+ });
4190
+
4191
+ export default InteractiveUtils;