@pie-lib/math-input-mathlive 1.1.5-beta.9 → 2.0.1-next.20261107131445

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 (195) hide show
  1. package/README.md +84 -109
  2. package/dist/_virtual/_rolldown/runtime.js +11 -0
  3. package/dist/horizontal-keypad.d.ts +22 -0
  4. package/dist/horizontal-keypad.js +54 -0
  5. package/dist/index.d.ts +20 -0
  6. package/dist/index.js +12 -0
  7. package/dist/keypad/index.d.ts +40 -0
  8. package/dist/keypad/index.js +245 -0
  9. package/dist/keypad/keys-layout.d.ts +7 -0
  10. package/dist/keypad/keys-layout.js +5 -0
  11. package/dist/keys/basic-operators.d.ts +5 -0
  12. package/dist/keys/basic-operators.js +30 -0
  13. package/dist/keys/chars.d.ts +5 -0
  14. package/dist/keys/comparison.d.ts +4 -0
  15. package/dist/keys/comparison.js +32 -0
  16. package/dist/keys/constants.d.ts +4 -0
  17. package/dist/keys/constants.js +35 -0
  18. package/dist/keys/digits.d.ts +15 -0
  19. package/dist/keys/digits.js +34 -0
  20. package/dist/keys/edit.d.ts +6 -0
  21. package/dist/keys/edit.js +9 -0
  22. package/dist/keys/exponent.d.ts +4 -0
  23. package/dist/keys/exponent.js +28 -0
  24. package/dist/keys/fractions.d.ts +3 -0
  25. package/dist/keys/fractions.js +27 -0
  26. package/dist/keys/geometry.d.ts +23 -0
  27. package/dist/keys/geometry.js +127 -0
  28. package/dist/keys/grades.d.ts +9 -0
  29. package/dist/keys/grades.js +414 -0
  30. package/dist/keys/index.d.ts +6 -0
  31. package/dist/keys/index.js +50 -0
  32. package/dist/keys/log.d.ts +3 -0
  33. package/dist/keys/log.js +25 -0
  34. package/dist/keys/logic.d.ts +2 -0
  35. package/dist/keys/logic.js +13 -0
  36. package/dist/keys/matrices.d.ts +2 -0
  37. package/dist/keys/matrices.js +17 -0
  38. package/dist/keys/misc.d.ts +10 -0
  39. package/dist/keys/misc.js +60 -0
  40. package/dist/keys/navigation.d.ts +2 -0
  41. package/dist/keys/navigation.js +13 -0
  42. package/dist/keys/operators.d.ts +1 -0
  43. package/dist/keys/operators.js +11 -0
  44. package/dist/keys/statistics.d.ts +5 -0
  45. package/dist/keys/statistics.js +38 -0
  46. package/dist/keys/sub-sup.d.ts +2 -0
  47. package/dist/keys/sub-sup.js +17 -0
  48. package/dist/keys/trigonometry.d.ts +6 -0
  49. package/dist/keys/trigonometry.js +43 -0
  50. package/dist/keys/utils.d.ts +5 -0
  51. package/dist/keys/utils.js +24 -0
  52. package/dist/keys/vars.d.ts +3 -0
  53. package/dist/keys/vars.js +22 -0
  54. package/dist/latex-bridge.d.ts +197 -0
  55. package/dist/latex-bridge.js +210 -0
  56. package/dist/longdiv.d.ts +24 -0
  57. package/dist/longdiv.js +43 -0
  58. package/dist/math-input.d.ts +26 -0
  59. package/dist/math-input.js +74 -0
  60. package/dist/mathlive-instance.d.ts +221 -0
  61. package/dist/mathlive-instance.js +159 -0
  62. package/dist/mf/common-styles.d.ts +594 -0
  63. package/dist/mf/common-styles.js +80 -0
  64. package/dist/mf/field-label.d.ts +16 -0
  65. package/dist/mf/field-label.js +13 -0
  66. package/dist/mf/index.d.ts +4 -0
  67. package/dist/mf/index.js +21 -0
  68. package/dist/mf/input.d.ts +94 -0
  69. package/dist/mf/input.js +127 -0
  70. package/dist/mf/static.d.ts +143 -0
  71. package/dist/mf/static.js +230 -0
  72. package/dist/selection.d.ts +43 -0
  73. package/dist/selection.js +39 -0
  74. package/package.json +32 -18
  75. package/lib/__tests__/composites.test.js +0 -255
  76. package/lib/__tests__/drop-in-compat.test.js +0 -133
  77. package/lib/__tests__/embedded-fonts.test.js +0 -121
  78. package/lib/__tests__/index.test.js +0 -118
  79. package/lib/__tests__/input-imperative.test.js +0 -155
  80. package/lib/__tests__/keypad-label.test.js +0 -231
  81. package/lib/__tests__/keypad-render.test.js +0 -206
  82. package/lib/__tests__/latex-bridge.test.js +0 -386
  83. package/lib/__tests__/mathlive-instance.test.js +0 -419
  84. package/lib/__tests__/mount-race.test.js +0 -218
  85. package/lib/__tests__/static-prompts.test.js +0 -405
  86. package/lib/__tests__/tmp-markup.test.js +0 -26
  87. package/lib/horizontal-keypad.js +0 -101
  88. package/lib/horizontal-keypad.js.map +0 -1
  89. package/lib/index.js +0 -143
  90. package/lib/index.js.map +0 -1
  91. package/lib/keypad/index.js +0 -431
  92. package/lib/keypad/index.js.map +0 -1
  93. package/lib/keypad/keys-layout.js +0 -22
  94. package/lib/keypad/keys-layout.js.map +0 -1
  95. package/lib/keys/basic-operators.js +0 -33
  96. package/lib/keys/basic-operators.js.map +0 -1
  97. package/lib/keys/chars.js +0 -12
  98. package/lib/keys/chars.js.map +0 -1
  99. package/lib/keys/comparison.js +0 -39
  100. package/lib/keys/comparison.js.map +0 -1
  101. package/lib/keys/constants.js +0 -37
  102. package/lib/keys/constants.js.map +0 -1
  103. package/lib/keys/digits.js +0 -46
  104. package/lib/keys/digits.js.map +0 -1
  105. package/lib/keys/edit.js +0 -14
  106. package/lib/keys/edit.js.map +0 -1
  107. package/lib/keys/exponent.js +0 -30
  108. package/lib/keys/exponent.js.map +0 -1
  109. package/lib/keys/fractions.js +0 -29
  110. package/lib/keys/fractions.js.map +0 -1
  111. package/lib/keys/geometry.js +0 -143
  112. package/lib/keys/geometry.js.map +0 -1
  113. package/lib/keys/grades.js +0 -259
  114. package/lib/keys/grades.js.map +0 -1
  115. package/lib/keys/index.js +0 -35
  116. package/lib/keys/index.js.map +0 -1
  117. package/lib/keys/log.js +0 -27
  118. package/lib/keys/log.js.map +0 -1
  119. package/lib/keys/logic.js +0 -19
  120. package/lib/keys/logic.js.map +0 -1
  121. package/lib/keys/matrices.js +0 -19
  122. package/lib/keys/matrices.js.map +0 -1
  123. package/lib/keys/misc.js +0 -69
  124. package/lib/keys/misc.js.map +0 -1
  125. package/lib/keys/navigation.js +0 -20
  126. package/lib/keys/navigation.js.map +0 -1
  127. package/lib/keys/operators.js +0 -15
  128. package/lib/keys/operators.js.map +0 -1
  129. package/lib/keys/statistics.js +0 -40
  130. package/lib/keys/statistics.js.map +0 -1
  131. package/lib/keys/sub-sup.js +0 -19
  132. package/lib/keys/sub-sup.js.map +0 -1
  133. package/lib/keys/trigonometry.js +0 -45
  134. package/lib/keys/trigonometry.js.map +0 -1
  135. package/lib/keys/utils.js +0 -87
  136. package/lib/keys/utils.js.map +0 -1
  137. package/lib/keys/vars.js +0 -24
  138. package/lib/keys/vars.js.map +0 -1
  139. package/lib/latex-bridge.js +0 -508
  140. package/lib/latex-bridge.js.map +0 -1
  141. package/lib/math-input.js +0 -143
  142. package/lib/math-input.js.map +0 -1
  143. package/lib/mathlive-instance.js +0 -585
  144. package/lib/mathlive-instance.js.map +0 -1
  145. package/lib/mf/common-styles.js +0 -203
  146. package/lib/mf/common-styles.js.map +0 -1
  147. package/lib/mf/index.js +0 -84
  148. package/lib/mf/index.js.map +0 -1
  149. package/lib/mf/input.js +0 -364
  150. package/lib/mf/input.js.map +0 -1
  151. package/lib/mf/static.js +0 -500
  152. package/lib/mf/static.js.map +0 -1
  153. package/src/__tests__/composites.test.jsx +0 -220
  154. package/src/__tests__/drop-in-compat.test.jsx +0 -112
  155. package/src/__tests__/index.test.jsx +0 -109
  156. package/src/__tests__/input-imperative.test.jsx +0 -144
  157. package/src/__tests__/keypad-label.test.jsx +0 -283
  158. package/src/__tests__/keypad-render.test.jsx +0 -123
  159. package/src/__tests__/latex-bridge.test.js +0 -389
  160. package/src/__tests__/mathlive-instance.test.js +0 -418
  161. package/src/__tests__/mount-race.test.jsx +0 -142
  162. package/src/__tests__/static-prompts.test.jsx +0 -364
  163. package/src/horizontal-keypad.jsx +0 -73
  164. package/src/index.jsx +0 -63
  165. package/src/keypad/index.jsx +0 -386
  166. package/src/keypad/keys-layout.js +0 -16
  167. package/src/keys/basic-operators.js +0 -32
  168. package/src/keys/chars.js +0 -5
  169. package/src/keys/comparison.js +0 -28
  170. package/src/keys/constants.js +0 -35
  171. package/src/keys/digits.js +0 -40
  172. package/src/keys/edit.js +0 -3
  173. package/src/keys/exponent.js +0 -28
  174. package/src/keys/fractions.js +0 -26
  175. package/src/keys/geometry.js +0 -149
  176. package/src/keys/grades.js +0 -367
  177. package/src/keys/index.js +0 -20
  178. package/src/keys/log.js +0 -22
  179. package/src/keys/logic.js +0 -15
  180. package/src/keys/matrices.js +0 -15
  181. package/src/keys/misc.js +0 -71
  182. package/src/keys/navigation.js +0 -8
  183. package/src/keys/operators.js +0 -10
  184. package/src/keys/statistics.js +0 -38
  185. package/src/keys/sub-sup.js +0 -15
  186. package/src/keys/trigonometry.js +0 -15
  187. package/src/keys/utils.js +0 -66
  188. package/src/keys/vars.js +0 -19
  189. package/src/latex-bridge.js +0 -524
  190. package/src/math-input.jsx +0 -115
  191. package/src/mathlive-instance.js +0 -582
  192. package/src/mf/common-styles.js +0 -210
  193. package/src/mf/index.js +0 -30
  194. package/src/mf/input.jsx +0 -319
  195. package/src/mf/static.jsx +0 -479
@@ -1,38 +0,0 @@
1
- import { mkSet } from './utils';
2
-
3
- const set = mkSet('statistics');
4
- export const xBar = set({
5
- name: 'X Bar',
6
- label: 'x̄',
7
- latex: '\\overline{x}',
8
- write: '\\overline{x}',
9
- });
10
-
11
- export const yBar = set({
12
- name: 'Y Bar',
13
- latex: '\\overline{y}',
14
- write: '\\overline{y}',
15
- });
16
-
17
- export const mu = set({
18
- name: 'mu',
19
- label: 'mu',
20
- latex: '\\mu',
21
- write: '\\mu',
22
- });
23
-
24
- export const sigma = set({
25
- name: 'Sigma',
26
- ariaLabel: 'Uppercase Sigma',
27
- label: '\\Sigma',
28
- latex: '\\Sigma',
29
- write: '\\Sigma',
30
- });
31
-
32
- export const smallSigma = set({
33
- name: 'sigma',
34
- ariaLabel: 'Lowercase Sigma',
35
- label: '\\sigma',
36
- latex: '\\sigma',
37
- write: '\\sigma',
38
- });
@@ -1,15 +0,0 @@
1
- import { mkSet } from './utils';
2
-
3
- const set = mkSet('sub-sup');
4
-
5
- export const superscript = set({
6
- name: 'Superscript',
7
- latex: 'x^{}',
8
- command: '^',
9
- });
10
-
11
- export const subscript = set({
12
- name: 'Subscript',
13
- latex: 'x_{}',
14
- command: '_',
15
- });
@@ -1,15 +0,0 @@
1
- import { mkSet } from './utils';
2
-
3
- const set = mkSet('trigonometry');
4
-
5
- export const sin = set({ name: 'sine', label: 'sin', command: '\\sin', latex: '\\sin' });
6
-
7
- export const cos = set({ name: 'cosine', label: 'cos', command: '\\cos', latex: '\\cos' });
8
-
9
- export const tan = set({ name: 'tanget', label: 'tan', command: '\\tan', latex: '\\tan' });
10
-
11
- export const sec = set({ name: 'secant', label: 'sec', command: '\\sec', latex: '\\sec' });
12
-
13
- export const csc = set({ name: 'cosecant', label: 'csc', command: '\\csc', latex: '\\csc' });
14
-
15
- export const cot = set({ name: 'cotangent', label: 'cot', command: '\\cot', latex: '\\cot' });
package/src/keys/utils.js DELETED
@@ -1,66 +0,0 @@
1
- import { flatten, times } from 'lodash-es';
2
-
3
- export const mkSet = (category) => (o) => ({ ...o, category });
4
-
5
- export const toArray = (o) => Object.keys(o).map((k) => o[k]);
6
-
7
- export const toKey = (category) => (v) => {
8
- if (typeof v === 'string') {
9
- return {
10
- name: v,
11
- label: v,
12
- category,
13
- write: v,
14
- };
15
- } else {
16
- return {
17
- ...v,
18
- category,
19
- };
20
- }
21
- };
22
-
23
- export const transformToKeySetStructure = (data = []) => {
24
- const structure = [];
25
-
26
- times(5, () => structure.push([]));
27
-
28
- let ln = data.length;
29
- let i = 0;
30
- let j = 0;
31
-
32
- while (j < ln) {
33
- structure[i++].push(data[j++]);
34
-
35
- if (i === 5) {
36
- i = 0;
37
- }
38
- }
39
-
40
- return structure;
41
- };
42
-
43
- const latexAndNameDontExist = (base) => (k) => {
44
- const flattened = flatten(base);
45
- const latexExists = flattened.some((b) => b.latex === k.latex);
46
- const nameExists = flattened.some((b) => b.name === k.name);
47
-
48
- return !latexExists && !nameExists;
49
- };
50
-
51
- export const extendKeySet = (base = [], keySetData = []) => {
52
- keySetData = keySetData.filter(latexAndNameDontExist(base));
53
-
54
- const final = [];
55
-
56
- times(5 - base.length, () => base.push([]));
57
- times(5, () => final.push([]));
58
-
59
- const extra = transformToKeySetStructure(keySetData);
60
-
61
- for (let i = 0; i < 5; i++) {
62
- final[i] = [...base[i], ...extra[i]];
63
- }
64
-
65
- return final;
66
- };
package/src/keys/vars.js DELETED
@@ -1,19 +0,0 @@
1
- import { mkSet } from './utils';
2
-
3
- const set = mkSet('vars');
4
-
5
- export const x = set({
6
- name: 'X',
7
- latex: 'x',
8
- write: 'x',
9
- });
10
- export const y = set({
11
- name: 'Y',
12
- latex: 'y',
13
- write: 'y',
14
- });
15
- export const theta = set({
16
- name: 'Theta',
17
- latex: '\\theta',
18
- write: '\\theta',
19
- });
@@ -1,524 +0,0 @@
1
- /**
2
- * LaTeX translation layer between the MathQuill-era content model and MathLive.
3
- *
4
- * Two independent concerns live here:
5
- *
6
- * 1. Newlines. MathQuill had no line break, so pie-lib registered a custom
7
- * `newLine` embed that serialises to `\embed{newLine}[]`. MathLive has no
8
- * embed system but supports real multiline environments, so we translate
9
- * `\embed{newLine}[]` <-> `\\` inside `\displaylines{}`.
10
- *
11
- * Authored content keeps its `\embed{newLine}[]` on-disk form (that is what
12
- * @pie-lib/math-rendering already normalises for MathJax), so each renderer
13
- * normalises at its own boundary and stored items stay compatible.
14
- *
15
- * 2. Keypad key -> MathLive action. MathQuill keys carry `write`/`command`/
16
- * `keystroke` built for `mf.write()` / `mf.cmd()` / `mf.keystroke()`.
17
- * MathLive has `insert()` and `executeCommand()` instead.
18
- */
19
-
20
- export const NEWLINE_EMBED = '\\embed{newLine}[]';
21
- const NEWLINE_EMBED_REGEX = /\\embed\{newLine\}\[\]/g;
22
- const DISPLAYLINES_REGEX = /^\\displaylines\{([\s\S]*)\}$/;
23
-
24
- /**
25
- * MathQuill's editable sub-field syntax, e.g. `\MathQuillMathField[r1]{}`.
26
- * These are the answer blocks in templated math items; MathLive expresses the
27
- * same idea as `\placeholder[id]{}` and reads them back via the prompts API.
28
- */
29
- const MQ_FIELD_REGEX = /\\MathQuillMathField\[([^\]]*)\]\{([^{}]*)\}/g;
30
- const PLACEHOLDER_REGEX = /\\placeholder\[([^\]]*)\]\{([^{}]*)\}/g;
31
-
32
- /**
33
- * Unnamed placeholders, which MathLive inserts on its own.
34
- *
35
- * Typing a command with empty arguments (`\longdiv{}`, `\frac{}{}`) makes
36
- * MathLive serialise the empty slots as `\placeholder{}`. That token is
37
- * MathLive-specific: MathJax - which @pie-lib/math-rendering uses to render
38
- * prompts and previews - has no such command and renders it as red error text.
39
- * Stored latex must therefore keep plain empty groups.
40
- */
41
- const BARE_PLACEHOLDER_REGEX = /\\placeholder\{([^{}]*)\}/g;
42
- const BARE_PLACEHOLDER_NO_ARG_REGEX = /\\placeholder(?![[{a-zA-Z])/g;
43
-
44
- /** Default id used when stored latex has an unnamed field. */
45
- export const DEFAULT_FIELD_ID = 'r1';
46
-
47
- /**
48
- * A visible stand-in for an empty argument slot, used for STATIC rendering only.
49
- *
50
- * Stored latex legitimately contains empty groups - `\longdiv{}`, `\frac{}{}`,
51
- * `x^{}` - to express a command's shape. MathQuill drew those as a shaded box
52
- * (`.mq-empty`); MathLive renders `{}` as nothing, so `convertLatexToMarkup`
53
- * output comes out completely blank (not even the surrounding bracket, in the
54
- * case of `\enclose{longdiv}{}`).
55
- *
56
- * `\placeholder{}` is not usable here: in static markup it renders as a
57
- * non-breaking space, because its editable box only exists inside a live
58
- * mathfield. So something with real width is required.
59
- *
60
- * It must be a `\rule`, not a unicode glyph. A glyph's width comes from font
61
- * metrics, and U+25AB is not in the KaTeX fonts - the browser substitutes a
62
- * wider fallback while MathLive still sizes surrounding accents from its own
63
- * metrics. That left stretchy accents (`\overarc`, `\overleftrightarrow`)
64
- * visibly narrower than the box they were meant to span. A rule carries an
65
- * explicit em width, so accents size correctly - and a filled box is closer to
66
- * MathQuill's `.mq-empty` anyway.
67
- *
68
- * Live mathfields need none of this - MathLive manages empty slots itself.
69
- */
70
- const EMPTY_SLOT = '\\htmlData{pie-empty=1}{\\rule{0.55em}{0.55em}}';
71
- const EMPTY_GROUP_REGEX = /\{\s*\}/g;
72
- // The segment key (`\overline{}`). A bar is a thin accent, so its label is shorter
73
- // than the ray and line keys' stretchy arrows. The keypad centres every label
74
- // in its cell, so the bar's box ended up ~0.17em higher than theirs.
75
- const EMPTY_OVERLINE_REGEX = /\\overline\{\s*\}/g;
76
- const EMPTY_OVERLINE_STRUT = '\\rule{0em}{1.08em}';
77
- // Empty delimiter pairs: `\abs{}` is converted to `\left|\right|`, and the
78
- // parenthesis / bracket keys are `\left(\right)` / `\left[\right]`. None has a
79
- // `{}` group for the rule above to catch, so they would render as bare fences.
80
- const EMPTY_FENCE_REGEX = /\\left([([|])\s*\\right([)\]|])/g;
81
- // The same muted fill as the other slots (`pie-empty`), but a tall rectangle
82
- // rather than a square. It also sets the fences' height: `\left...\right` stretch
83
- // to their content, so a taller slot gives taller parentheses.
84
- //
85
- // A `\rule` stands on the baseline, but the fences are centred on the math axis
86
- // (0.25em above it), so an un-lifted box sat high between them. `[-0.25em]`
87
- // lowers it by (axis - height / 2) = 0.25 - 1 / 2, centring it on the axis.
88
- //
89
- // Lowering it alone also shrinks the fences, which size themselves to their
90
- // content (MathLive switches to short bars). The zero-width `\rule{0em}{1em}`
91
- // strut keeps the content as tall as before, so the bars keep their height and
92
- // only the box moves.
93
- const EMPTY_FENCE_SLOT = '\\rule{0em}{1em}\\htmlData{pie-empty=1}{\\rule[-0.25em]{0.55em}{1em}}';
94
-
95
- /**
96
- * Make empty argument slots visible, for static rendering.
97
- *
98
- * Display-only: never feed the result to a mathfield or to storage - the
99
- * original latex is what gets typed and saved.
100
- *
101
- * @param {string} latex
102
- * @returns {string}
103
- */
104
- export const withVisibleEmptySlots = (latex) =>
105
- (latex || '')
106
- .replace(EMPTY_OVERLINE_REGEX, `\\overline{${EMPTY_OVERLINE_STRUT}${EMPTY_SLOT}}`)
107
- .replace(EMPTY_GROUP_REGEX, `{${EMPTY_SLOT}}`)
108
- .replace(EMPTY_FENCE_REGEX, (_m, open, close) => `\\left${open}${EMPTY_FENCE_SLOT}\\right${close}`);
109
-
110
- /**
111
- * Pie commands that take an argument, and their native MathLive equivalents.
112
- *
113
- * These MUST NOT be handled as MathLive macros. A macro is serialised from the
114
- * arguments it was created with, so text typed inside its expansion never makes
115
- * it back out: `getValue('latex')` keeps returning `\longdiv{\placeholder{}}`
116
- * while `getValue('ascii-math')` shows the typed content. Setting
117
- * `captureSelection: false` lets the cursor in but does not change
118
- * serialisation.
119
- *
120
- * Expanding to native constructs instead gives real, editable atoms that
121
- * round-trip. All three targets are understood by MathJax too, so stored latex
122
- * still renders in @pie-lib/math-rendering without a reverse mapping.
123
- */
124
- /**
125
- * Rewrite argument-taking pie commands into native MathLive latex.
126
- * Applied on the way *into* a mathfield.
127
- *
128
- * @param {string} latex
129
- * @returns {string}
130
- */
131
- export const toNativeCommands = (latex) => {
132
- if (typeof latex !== 'string' || !latex) {
133
- return latex || '';
134
- }
135
-
136
- let out = latex;
137
-
138
- // \abs{x} -> \left|x\right| needs brace-aware handling, the rest are prefix
139
- // swaps.
140
- out = replaceAbs(out);
141
- out = out.replace(/\\longdiv\{/g, '\\enclose{longdiv}{');
142
- out = out.replace(/\\overarc\{/g, '\\overparen{');
143
- out = escapeBareComments(out);
144
-
145
- return out;
146
- };
147
-
148
- /**
149
- * Escape an unescaped `%`.
150
- *
151
- * In real LaTeX `%` starts a comment, so MathLive discards it *and everything
152
- * after it*: `50%` renders as `50`, and `x%y` as just `x`. MathQuill had no
153
- * such notion - it treated `%` as a symbol - so latex from anywhere outside the
154
- * keypad may still carry a bare one.
155
- *
156
- * Only a `%` preceded by an even number of backslashes is bare: `\%` is already
157
- * escaped, while the `%` in `\\%` (a line break, then a comment) is not.
158
- *
159
- * @param {string} latex
160
- * @returns {string}
161
- */
162
- const escapeBareComments = (latex) => {
163
- if (latex.indexOf('%') === -1) {
164
- return latex;
165
- }
166
-
167
- let out = '';
168
- // Consecutive backslashes immediately before the current character.
169
- let backslashes = 0;
170
-
171
- for (let i = 0; i < latex.length; i++) {
172
- const ch = latex[i];
173
-
174
- out += ch === '%' && backslashes % 2 === 0 ? '\\%' : ch;
175
- backslashes = ch === '\\' ? backslashes + 1 : 0;
176
- }
177
-
178
- return out;
179
- };
180
-
181
- /** `\abs{x}` -> `\left|x\right|`, matching balanced braces. */
182
- const replaceAbs = (latex) => {
183
- let out = latex;
184
- let idx = out.indexOf('\\abs{');
185
-
186
- while (idx !== -1) {
187
- const open = idx + '\\abs{'.length - 1;
188
- let depth = 0;
189
- let close = -1;
190
-
191
- for (let i = open; i < out.length; i++) {
192
- if (out[i] === '{') depth++;
193
- else if (out[i] === '}') {
194
- depth--;
195
- if (depth === 0) {
196
- close = i;
197
- break;
198
- }
199
- }
200
- }
201
-
202
- if (close === -1) {
203
- break;
204
- }
205
-
206
- const inner = out.slice(open + 1, close);
207
-
208
- out = `${out.slice(0, idx)}\\left|${inner}\\right|${out.slice(close + 1)}`;
209
- idx = out.indexOf('\\abs{');
210
- }
211
-
212
- return out;
213
- };
214
-
215
- /**
216
- * Ids of the answer blocks in a piece of stored latex, in document order.
217
- *
218
- * @param {string} latex
219
- * @returns {string[]}
220
- */
221
- export const fieldIds = (latex) => {
222
- if (typeof latex !== 'string') {
223
- return [];
224
- }
225
-
226
- const out = [];
227
- let match;
228
-
229
- MQ_FIELD_REGEX.lastIndex = 0;
230
-
231
- while ((match = MQ_FIELD_REGEX.exec(latex)) !== null) {
232
- out.push(match[1] || DEFAULT_FIELD_ID);
233
- }
234
-
235
- return out;
236
- };
237
-
238
- /**
239
- * Convert stored latex (which may contain `\embed{newLine}[]`) into latex that
240
- * MathLive can parse and render as multiple lines.
241
- *
242
- * @param {string} latex
243
- * @returns {string}
244
- */
245
- export const toMathLive = (latex) => {
246
- if (typeof latex !== 'string' || !latex) {
247
- return latex || '';
248
- }
249
-
250
- // Argument-taking pie commands -> native constructs, so their content stays
251
- // editable and serialises back correctly (see toNativeCommands).
252
- let out = toNativeCommands(latex);
253
-
254
- // Answer blocks: \MathQuillMathField[id]{x} -> \placeholder[id]{x}
255
- out = out.replace(MQ_FIELD_REGEX, (_m, id, content) => `\\placeholder[${id || DEFAULT_FIELD_ID}]{${content}}`);
256
-
257
- // Newlines: \embed{newLine}[] -> a multiline environment
258
- if (out.indexOf(NEWLINE_EMBED) !== -1) {
259
- out = `\\displaylines{${out.split(NEWLINE_EMBED_REGEX).join(' \\\\ ')}}`;
260
- }
261
-
262
- return out;
263
- };
264
-
265
- /**
266
- * Keypad command names that MathQuill treated as ALIASES of a different command.
267
- *
268
- * MathQuill registered `\\divide` as another name for `\\div` (and
269
- * `\\perpendicular` for `\\perp`), so pressing the key stored the real command.
270
- * MathLive only knows these names through `PIE_MACROS`, and serialises a macro
271
- * under its own name - so `\\divide` reached storage verbatim, where MathJax
272
- * (@pie-lib/math-rendering) has no such command and renders it as red text.
273
- *
274
- * Every other keypad command already matches what MathQuill stored (`\\degree`,
275
- * `\\nsim`, `\\ncong`, `\\nparallel`, `\\square`, `\\napprox`,
276
- * `\\parallelogram`), and MathJax understands those.
277
- */
278
- const MATHQUILL_ALIASES = {
279
- '\\divide': '\\div',
280
- '\\perpendicular': '\\perp',
281
- };
282
-
283
- // Whole command names only: `\\divideontimes` is a real AMS symbol.
284
- const MATHQUILL_ALIAS_REGEX = /\\(divide|perpendicular)(?![a-zA-Z])/g;
285
-
286
- /**
287
- * Primes. MathLive turns a prime that follows a base into a superscript atom and
288
- * serialises it as `^{\\prime}`, or `^{\\doubleprime}` for the keypad's double
289
- * prime key (`5''` comes back out as `5^{\\doubleprime}`). MathJax has no
290
- * `\\doubleprime`, and two adjacent `^{\\prime}` groups - what typing `'` twice
291
- * produces - are a "double exponent" error. MathQuill stored plain apostrophes,
292
- * which both MathJax and MathLive read as primes, so store those.
293
- *
294
- * Only a superscript made of primes alone is rewritten: `x^{\\prime 2}` keeps
295
- * its group, since it holds more than primes.
296
- */
297
- const PRIME_SUPERSCRIPT_REGEX = /\^\{((?:\s*\\(?:prime|doubleprime)(?![a-zA-Z]))+)\s*\}/g;
298
- const BARE_DOUBLEPRIME_REGEX = /\\doubleprime(?![a-zA-Z])/g;
299
-
300
- const primesToApostrophes = (primes) =>
301
- // eslint-disable-next-line quotes
302
- "'".repeat(
303
- (primes.match(/\\prime(?![a-zA-Z])/g) || []).length + 2 * (primes.match(BARE_DOUBLEPRIME_REGEX) || []).length,
304
- );
305
-
306
- const BARE_PRIME_COMMAND_REGEX = /^\\(doubleprime|prime)(?![a-zA-Z])/;
307
-
308
- /**
309
- * Primes that are *not* in a superscript.
310
- *
311
- * MathLive emits bare `\prime` / `\doubleprime` for a `′` or `″` typed or pasted
312
- * as a character (`5′6″` serialises as `5\prime6\doubleprime`). A bare `\prime`
313
- * is a full-size glyph in MathJax, while `''` is a superscript, so storing
314
- * `5\prime6''` drew the single prime visibly larger than each stroke of the
315
- * double prime. Both become apostrophes, the superscript primes MathQuill
316
- * stored, so they always match.
317
- *
318
- * Inside a script (`x^{\prime 2}`, `x^\prime`) a prime is already a
319
- * superscript, and an apostrophe there would mean something else, so
320
- * `\prime` is kept and `\doubleprime` (unknown to MathJax) becomes
321
- * `\prime\prime`.
322
- *
323
- * @param {string} latex
324
- * @returns {string}
325
- */
326
- const barePrimesToApostrophes = (latex) => {
327
- if (latex.indexOf('prime') === -1) {
328
- return latex;
329
- }
330
-
331
- // One entry per open brace: is that group a super/subscript?
332
- const groups = [];
333
- let out = '';
334
- let i = 0;
335
-
336
- while (i < latex.length) {
337
- const ch = latex[i];
338
-
339
- if (ch === '\\') {
340
- const match = latex.slice(i).match(BARE_PRIME_COMMAND_REGEX);
341
-
342
- if (match) {
343
- const isDouble = match[1] === 'doubleprime';
344
- // `x^\prime`: the command is itself the script.
345
- const isScriptArgument = /[\^_]\s*$/.test(out);
346
- const inScript = isScriptArgument || groups.indexOf(true) !== -1;
347
-
348
- if (inScript) {
349
- out += isDouble ? '\\prime\\prime' : '\\prime';
350
- } else {
351
- // eslint-disable-next-line quotes
352
- out += isDouble ? "''" : "'";
353
- }
354
-
355
- i += match[0].length;
356
- continue;
357
- }
358
-
359
- // Copy any other command, or an escaped character such as `\{`, whole.
360
- const command = latex.slice(i).match(/^\\(?:[a-zA-Z]+|.)/);
361
- const text = command ? command[0] : ch;
362
-
363
- out += text;
364
- i += text.length;
365
- continue;
366
- }
367
-
368
- if (ch === '{') {
369
- groups.push(/[\^_]\s*$/.test(out));
370
- } else if (ch === '}') {
371
- groups.pop();
372
- }
373
-
374
- out += ch;
375
- i += 1;
376
- }
377
-
378
- return out;
379
- };
380
-
381
- /**
382
- * Rewrite what MathLive serialises into the form MathQuill stored: alias
383
- * command names, and primes.
384
- *
385
- * @param {string} latex
386
- * @returns {string}
387
- */
388
- export const toCanonicalCommands = (latex) =>
389
- typeof latex === 'string'
390
- ? barePrimesToApostrophes(
391
- latex
392
- .replace(MATHQUILL_ALIAS_REGEX, (m) => MATHQUILL_ALIASES[m])
393
- .replace(PRIME_SUPERSCRIPT_REGEX, (_m, primes) => primesToApostrophes(primes)),
394
- )
395
- : latex;
396
-
397
- /**
398
- * Convert latex out of MathLive back into the stored form, so items authored
399
- * with the MathLive editor remain readable by @pie-lib/math-rendering and by
400
- * the existing MathQuill implementation.
401
- *
402
- * @param {string} latex
403
- * @returns {string}
404
- */
405
- export const fromMathLive = (latex) => {
406
- if (typeof latex !== 'string' || !latex) {
407
- return latex || '';
408
- }
409
-
410
- const match = latex.trim().match(DISPLAYLINES_REGEX);
411
- let out = match ? match[1] : latex;
412
-
413
- // `\\` is the line separator inside a multiline environment.
414
- if (out.indexOf('\\\\') !== -1) {
415
- out = out
416
- .split(/\s*\\\\\s*/)
417
- .map((s) => s.trim())
418
- .join(NEWLINE_EMBED);
419
- }
420
-
421
- // Answer blocks: \placeholder[id]{x} -> \MathQuillMathField[id]{x}
422
- out = out.replace(
423
- PLACEHOLDER_REGEX,
424
- (_m, id, content) => `\\MathQuillMathField[${id || DEFAULT_FIELD_ID}]{${content}}`,
425
- );
426
-
427
- // Unnamed placeholders are MathLive's own representation of an empty slot and
428
- // must not reach storage or MathJax. Unwrap them: keep any content, drop the
429
- // command itself (the surrounding braces are already part of the host latex,
430
- // so re-adding them here would produce `\longdiv{{}}`).
431
- out = out.replace(BARE_PLACEHOLDER_REGEX, (_m, content) => content);
432
- out = out.replace(BARE_PLACEHOLDER_NO_ARG_REGEX, '');
433
-
434
- // Store the command MathQuill stored, not the alias - also heals latex that was
435
- // saved with `\\divide` before the keypad resolved it.
436
- return toCanonicalCommands(out);
437
- };
438
-
439
- /**
440
- * MathQuill `cmd()` semantics: commands that take arguments should land the
441
- * cursor inside the first argument. MathLive expresses that with `#?`
442
- * placeholders in the inserted latex.
443
- */
444
- const COMMANDS_WITH_ARGS = {
445
- // An explicit "blank over blank" key: both slots start empty.
446
- '\\frac': '\\frac{#?}{#?}',
447
- // Division, which must behave like MathQuill's `/`: whatever precedes the
448
- // cursor becomes the numerator and the caret lands in the denominator. `#@`
449
- // is MathLive's token for "the selection, or the item before the cursor" -
450
- // this is exactly the template MathLive binds to the `/` key itself. Using
451
- // `#?` here instead produced an empty fraction and left the typed number
452
- // stranded outside it.
453
- '/': '\\frac{#@}{#?}',
454
- '\\sqrt': '\\sqrt{#?}',
455
- '\\nthroot': '\\sqrt[#?]{#?}',
456
- '\\overline': '\\overline{#?}',
457
- '\\overrightarrow': '\\overrightarrow{#?}',
458
- '\\overleftrightarrow': '\\overleftrightarrow{#?}',
459
- // Native forms, not the pie macros: a macro would not serialise the user's
460
- // typed content back out (see toNativeCommands).
461
- '\\overarc': '\\overparen{#?}',
462
- '\\longdiv': '\\enclose{longdiv}{#?}',
463
- '^': '^{#?}',
464
- _: '_{#?}',
465
- '(': '\\left(#?\\right)',
466
- '[': '\\left[#?\\right]',
467
- '|': '\\left|#?\\right|',
468
- };
469
-
470
- /**
471
- * MathQuill keystroke names -> MathLive command selectors.
472
- */
473
- const KEYSTROKES = {
474
- Left: 'moveToPreviousChar',
475
- Right: 'moveToNextChar',
476
- Backspace: 'deleteBackward',
477
- Delete: 'deleteForward',
478
- Up: 'moveUp',
479
- Down: 'moveDown',
480
- };
481
-
482
- /**
483
- * Translate a keypad key definition into a MathLive action.
484
- *
485
- * Mirrors the precedence in @pie-lib/math-input's MathInput.keypadPress:
486
- * latex (unless a command is present) -> write -> command -> keystroke.
487
- *
488
- * @param {object} key
489
- * @returns {{type: 'insert'|'command', value: string}|undefined}
490
- */
491
- export const keyToAction = (key) => {
492
- if (!key) {
493
- return undefined;
494
- }
495
-
496
- if (key.latex && !key.command) {
497
- return { type: 'insert', value: key.latex };
498
- }
499
-
500
- if (key.write) {
501
- return { type: 'insert', value: key.write };
502
- }
503
-
504
- if (key.command) {
505
- // Some keys carry an array of commands (e.g. Measured Angle is
506
- // ['m', '\\angle'], log-base-n is ['\\log', '_']). MathQuill's `cmd()`
507
- // applied them in sequence; MathLive inserts one latex string, so expand
508
- // each part and concatenate.
509
- const parts = Array.isArray(key.command) ? key.command : [key.command];
510
- const value = parts.map((c) => COMMANDS_WITH_ARGS[c] || MATHQUILL_ALIASES[c] || c).join('');
511
-
512
- return { type: 'insert', value };
513
- }
514
-
515
- if (key.keystroke) {
516
- const selector = KEYSTROKES[key.keystroke];
517
-
518
- return selector ? { type: 'command', value: selector } : undefined;
519
- }
520
-
521
- return undefined;
522
- };
523
-
524
- export { COMMANDS_WITH_ARGS, KEYSTROKES, MATHQUILL_ALIASES };