@pie-lib/math-input-mathlive 1.1.5-beta.8 → 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 (202) 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__/input-selection.test.js +0 -103
  81. package/lib/__tests__/keypad-label.test.js +0 -231
  82. package/lib/__tests__/keypad-render.test.js +0 -206
  83. package/lib/__tests__/latex-bridge.test.js +0 -386
  84. package/lib/__tests__/mathlive-instance.test.js +0 -419
  85. package/lib/__tests__/mount-race.test.js +0 -218
  86. package/lib/__tests__/selection.test.js +0 -195
  87. package/lib/__tests__/static-prompts.test.js +0 -405
  88. package/lib/__tests__/tmp-markup.test.js +0 -26
  89. package/lib/horizontal-keypad.js +0 -101
  90. package/lib/horizontal-keypad.js.map +0 -1
  91. package/lib/index.js +0 -143
  92. package/lib/index.js.map +0 -1
  93. package/lib/keypad/index.js +0 -431
  94. package/lib/keypad/index.js.map +0 -1
  95. package/lib/keypad/keys-layout.js +0 -22
  96. package/lib/keypad/keys-layout.js.map +0 -1
  97. package/lib/keys/basic-operators.js +0 -33
  98. package/lib/keys/basic-operators.js.map +0 -1
  99. package/lib/keys/chars.js +0 -12
  100. package/lib/keys/chars.js.map +0 -1
  101. package/lib/keys/comparison.js +0 -39
  102. package/lib/keys/comparison.js.map +0 -1
  103. package/lib/keys/constants.js +0 -37
  104. package/lib/keys/constants.js.map +0 -1
  105. package/lib/keys/digits.js +0 -46
  106. package/lib/keys/digits.js.map +0 -1
  107. package/lib/keys/edit.js +0 -14
  108. package/lib/keys/edit.js.map +0 -1
  109. package/lib/keys/exponent.js +0 -30
  110. package/lib/keys/exponent.js.map +0 -1
  111. package/lib/keys/fractions.js +0 -29
  112. package/lib/keys/fractions.js.map +0 -1
  113. package/lib/keys/geometry.js +0 -143
  114. package/lib/keys/geometry.js.map +0 -1
  115. package/lib/keys/grades.js +0 -259
  116. package/lib/keys/grades.js.map +0 -1
  117. package/lib/keys/index.js +0 -35
  118. package/lib/keys/index.js.map +0 -1
  119. package/lib/keys/log.js +0 -27
  120. package/lib/keys/log.js.map +0 -1
  121. package/lib/keys/logic.js +0 -19
  122. package/lib/keys/logic.js.map +0 -1
  123. package/lib/keys/matrices.js +0 -19
  124. package/lib/keys/matrices.js.map +0 -1
  125. package/lib/keys/misc.js +0 -69
  126. package/lib/keys/misc.js.map +0 -1
  127. package/lib/keys/navigation.js +0 -20
  128. package/lib/keys/navigation.js.map +0 -1
  129. package/lib/keys/operators.js +0 -15
  130. package/lib/keys/operators.js.map +0 -1
  131. package/lib/keys/statistics.js +0 -40
  132. package/lib/keys/statistics.js.map +0 -1
  133. package/lib/keys/sub-sup.js +0 -19
  134. package/lib/keys/sub-sup.js.map +0 -1
  135. package/lib/keys/trigonometry.js +0 -45
  136. package/lib/keys/trigonometry.js.map +0 -1
  137. package/lib/keys/utils.js +0 -87
  138. package/lib/keys/utils.js.map +0 -1
  139. package/lib/keys/vars.js +0 -24
  140. package/lib/keys/vars.js.map +0 -1
  141. package/lib/latex-bridge.js +0 -445
  142. package/lib/latex-bridge.js.map +0 -1
  143. package/lib/math-input.js +0 -143
  144. package/lib/math-input.js.map +0 -1
  145. package/lib/mathlive-instance.js +0 -585
  146. package/lib/mathlive-instance.js.map +0 -1
  147. package/lib/mf/common-styles.js +0 -203
  148. package/lib/mf/common-styles.js.map +0 -1
  149. package/lib/mf/index.js +0 -84
  150. package/lib/mf/index.js.map +0 -1
  151. package/lib/mf/input.js +0 -372
  152. package/lib/mf/input.js.map +0 -1
  153. package/lib/mf/static.js +0 -500
  154. package/lib/mf/static.js.map +0 -1
  155. package/lib/selection.js +0 -148
  156. package/lib/selection.js.map +0 -1
  157. package/src/__tests__/composites.test.jsx +0 -220
  158. package/src/__tests__/drop-in-compat.test.jsx +0 -112
  159. package/src/__tests__/index.test.jsx +0 -109
  160. package/src/__tests__/input-imperative.test.jsx +0 -144
  161. package/src/__tests__/input-selection.test.jsx +0 -64
  162. package/src/__tests__/keypad-label.test.jsx +0 -283
  163. package/src/__tests__/keypad-render.test.jsx +0 -123
  164. package/src/__tests__/latex-bridge.test.js +0 -359
  165. package/src/__tests__/mathlive-instance.test.js +0 -418
  166. package/src/__tests__/mount-race.test.jsx +0 -142
  167. package/src/__tests__/selection.test.js +0 -212
  168. package/src/__tests__/static-prompts.test.jsx +0 -364
  169. package/src/horizontal-keypad.jsx +0 -73
  170. package/src/index.jsx +0 -63
  171. package/src/keypad/index.jsx +0 -386
  172. package/src/keypad/keys-layout.js +0 -16
  173. package/src/keys/basic-operators.js +0 -32
  174. package/src/keys/chars.js +0 -5
  175. package/src/keys/comparison.js +0 -28
  176. package/src/keys/constants.js +0 -35
  177. package/src/keys/digits.js +0 -40
  178. package/src/keys/edit.js +0 -3
  179. package/src/keys/exponent.js +0 -28
  180. package/src/keys/fractions.js +0 -26
  181. package/src/keys/geometry.js +0 -149
  182. package/src/keys/grades.js +0 -367
  183. package/src/keys/index.js +0 -20
  184. package/src/keys/log.js +0 -22
  185. package/src/keys/logic.js +0 -15
  186. package/src/keys/matrices.js +0 -15
  187. package/src/keys/misc.js +0 -71
  188. package/src/keys/navigation.js +0 -8
  189. package/src/keys/operators.js +0 -10
  190. package/src/keys/statistics.js +0 -38
  191. package/src/keys/sub-sup.js +0 -15
  192. package/src/keys/trigonometry.js +0 -15
  193. package/src/keys/utils.js +0 -66
  194. package/src/keys/vars.js +0 -19
  195. package/src/latex-bridge.js +0 -449
  196. package/src/math-input.jsx +0 -115
  197. package/src/mathlive-instance.js +0 -582
  198. package/src/mf/common-styles.js +0 -210
  199. package/src/mf/index.js +0 -30
  200. package/src/mf/input.jsx +0 -328
  201. package/src/mf/static.jsx +0 -479
  202. package/src/selection.js +0 -146
@@ -1,212 +0,0 @@
1
- import { expandRange, trackWholeAtomSelection } from '../selection';
2
-
3
- /**
4
- * Offset tables captured from MathLive 0.110.0 `getElementInfo(i)` in Chrome,
5
- * as [depth, latex] per offset. Offset `i` is the atom ending at caret `i`.
6
- */
7
- const table = (rows) => (i) => (rows[i] ? { depth: rows[i][0], latex: rows[i][1] } : undefined);
8
-
9
- // 5x^2+2x+\frac{\pi}{2}-4
10
- const POLY = table([
11
- [0, ''], [0, '5'], [0, 'x'], [1, ''], [1, '2'], [0, '^2'], [0, '+'], [0, '2'], [0, 'x'], [0, '+'],
12
- [1, ''], [1, '\\pi'], [1, ''], [1, '2'], [0, '\\frac{\\pi}{2}'], [0, '-'], [0, '4'],
13
- ]);
14
- const POLY_LAST = 16;
15
-
16
- // a+\sqrt{x+1}+b
17
- const ROOT = table([[0, ''], [0, 'a'], [0, '+'], [1, ''], [1, 'x'], [1, '+'], [1, '1'], [0, '\\sqrt{x+1}'], [0, '+'], [0, 'b']]);
18
-
19
- // \frac{\frac{a}{b}}{c}
20
- const NESTED = table([
21
- [0, ''], [1, ''], [2, ''], [2, 'a'], [2, ''], [2, 'b'], [1, '\\frac{a}{b}'], [1, ''], [1, 'c'], [0, '\\frac{\\frac{a}{b}}{c}'],
22
- ]);
23
-
24
- describe('expandRange', () => {
25
- const poly = (a, b) => expandRange(a, b, POLY, POLY_LAST);
26
-
27
- it('leaves a selection of whole sibling atoms alone', () => {
28
- expect(poly(6, 7)).toEqual([6, 7]); // 2
29
- expect(poly(6, 9)).toEqual([6, 9]); // 2x+
30
- expect(poly(6, 15)).toEqual([6, 15]); // 2x+\frac{\pi}{2}-
31
- expect(poly(0, 16)).toEqual([0, 16]);
32
- });
33
-
34
- // The reported case: five Shift+Right presses from before `2x` gave `2x+\pi`.
35
- it('takes the whole fraction when the selection reaches into it', () => {
36
- expect(poly(6, 10)).toEqual([6, 14]);
37
- expect(poly(6, 11)).toEqual([6, 14]);
38
- expect(poly(6, 13)).toEqual([6, 14]);
39
- });
40
-
41
- it('takes the whole fraction when the selection leaves it', () => {
42
- expect(poly(11, 15)).toEqual([9, 15]); // from the pi to after the minus
43
- });
44
-
45
- it('crossing from numerator to denominator selects the fraction', () => {
46
- expect(poly(11, 13)).toEqual([9, 14]);
47
- expect(poly(11, 12)).toEqual([9, 14]);
48
- });
49
-
50
- it('a selection inside one branch stays inside it', () => {
51
- expect(poly(10, 11)).toEqual([10, 11]); // \pi
52
- expect(poly(12, 13)).toEqual([12, 13]); // the 2 of the denominator
53
- });
54
-
55
- // MathLive stores x^2 as `x` then a separate `^2` atom.
56
- it('keeps a superscript with its base', () => {
57
- expect(poly(3, 7)).toEqual([1, 7]); // x^2+2, not ^2+2
58
- expect(poly(4, 6)).toEqual([1, 6]); // x^2+
59
- });
60
-
61
- it('accepts a reversed range', () => {
62
- expect(poly(11, 6)).toEqual([6, 14]);
63
- });
64
-
65
- it('leaves a collapsed range alone', () => {
66
- expect(poly(11, 11)).toEqual([11, 11]);
67
- });
68
-
69
- it('handles roots the same way', () => {
70
- expect(expandRange(2, 4, ROOT, 9)).toEqual([2, 7]);
71
- expect(expandRange(4, 6, ROOT, 9)).toEqual([4, 6]); // +1 inside the radicand
72
- });
73
-
74
- it('climbs as many levels as needed in nested fractions', () => {
75
- expect(expandRange(2, 3, NESTED, 9)).toEqual([2, 3]); // a
76
- expect(expandRange(3, 5, NESTED, 9)).toEqual([1, 6]); // a/b crossing -> inner fraction
77
- expect(expandRange(6, 7, NESTED, 9)).toEqual([0, 9]); // inner -> outer denominator
78
- });
79
-
80
- it('tolerates missing info', () => {
81
- expect(expandRange(1, 3, () => undefined, 5)).toEqual([1, 3]);
82
- });
83
- });
84
-
85
- describe('trackWholeAtomSelection', () => {
86
- const stubField = (range, direction = 'forward') => {
87
- const listeners = {};
88
-
89
- return {
90
- selection: { ranges: [range], direction },
91
- get selectionIsCollapsed() {
92
- return this.selection.ranges[0][0] === this.selection.ranges[0][1];
93
- },
94
- lastOffset: POLY_LAST,
95
- getElementInfo: POLY,
96
- sets: 0,
97
- addEventListener(type, fn) {
98
- listeners[type] = fn;
99
- },
100
- removeEventListener(type, fn) {
101
- if (listeners[type] === fn) {
102
- delete listeners[type];
103
- }
104
- },
105
- fire(type) {
106
- if (listeners[type]) {
107
- listeners[type]();
108
- }
109
- },
110
- has(type) {
111
- return !!listeners[type];
112
- },
113
- };
114
- };
115
-
116
- const track = (field) => {
117
- // count assignments without breaking the getter
118
- let current = field.selection;
119
-
120
- Object.defineProperty(field, 'selection', {
121
- get: () => current,
122
- set: (v) => {
123
- field.sets += 1;
124
- current = v;
125
- },
126
- });
127
-
128
- return trackWholeAtomSelection(field);
129
- };
130
-
131
- it('widens the selection on selection-change', () => {
132
- const field = stubField([6, 11]);
133
-
134
- track(field);
135
- field.fire('selection-change');
136
-
137
- expect(field.selection.ranges).toEqual([[6, 14]]);
138
- });
139
-
140
- it('preserves the direction, so Shift+Arrow keeps extending the same end', () => {
141
- const field = stubField([11, 16], 'backward');
142
-
143
- track(field);
144
- field.fire('selection-change');
145
-
146
- expect(field.selection).toEqual({ ranges: [[9, 16]], direction: 'backward' });
147
- });
148
-
149
- it('does not touch a selection that is already whole', () => {
150
- const field = stubField([6, 9]);
151
-
152
- track(field);
153
- field.fire('selection-change');
154
-
155
- expect(field.sets).toBe(0);
156
- });
157
-
158
- it('ignores a collapsed caret', () => {
159
- const field = stubField([11, 11]);
160
-
161
- track(field);
162
- field.fire('selection-change');
163
-
164
- expect(field.sets).toBe(0);
165
- });
166
-
167
- it('does not recurse when its own assignment fires selection-change', () => {
168
- const field = stubField([6, 11]);
169
- let current = field.selection;
170
-
171
- Object.defineProperty(field, 'selection', {
172
- get: () => current,
173
- set: (v) => {
174
- field.sets += 1;
175
- current = v;
176
- field.fire('selection-change');
177
- },
178
- });
179
-
180
- trackWholeAtomSelection(field);
181
- field.fire('selection-change');
182
-
183
- expect(field.sets).toBe(1);
184
- expect(field.selection.ranges).toEqual([[6, 14]]);
185
- });
186
-
187
- it('survives a field that throws', () => {
188
- const field = stubField([6, 11]);
189
-
190
- field.getElementInfo = () => {
191
- throw new Error('boom');
192
- };
193
-
194
- track(field);
195
-
196
- expect(() => field.fire('selection-change')).not.toThrow();
197
- });
198
-
199
- it('tears down its listener', () => {
200
- const field = stubField([6, 11]);
201
- const untrack = track(field);
202
-
203
- expect(field.has('selection-change')).toBe(true);
204
- untrack();
205
- expect(field.has('selection-change')).toBe(false);
206
- });
207
-
208
- it('is a no-op without a field', () => {
209
- expect(trackWholeAtomSelection(undefined)).toBeUndefined();
210
- expect(trackWholeAtomSelection({})).toBeUndefined();
211
- });
212
- });
@@ -1,364 +0,0 @@
1
- import Static from '../mf/static';
2
-
3
- /**
4
- * The answer-block contract: `\MathQuillMathField[id]{}` in stored latex becomes
5
- * a MathLive prompt, and sub-field values/focus are surfaced through the same
6
- * callbacks @pie-lib/math-input exposed (`onSubFieldChange`, `onSubFieldFocus`,
7
- * `getFieldName`, `setInput`).
8
- *
9
- * Driven against a stub mathfield: a real <math-field> needs a browser, and what
10
- * matters here is the mapping, not MathLive's rendering.
11
- */
12
- const stubField = (values = {}, selection = null) => ({
13
- values: { ...values },
14
- getPrompts() {
15
- return Object.keys(this.values);
16
- },
17
- getPromptValue(id) {
18
- return this.values[id];
19
- },
20
- setPromptValue(id, v) {
21
- this.values[id] = v;
22
- },
23
- getPromptRange(id) {
24
- const i = Object.keys(this.values).indexOf(id);
25
-
26
- return [i * 10, i * 10 + 5];
27
- },
28
- selection,
29
- // Caret bookkeeping: MathLive exposes the caret offset as `position`, and any
30
- // executeCommand triggers the render that reveals the caret.
31
- position: undefined,
32
- commands: [],
33
- executeCommand(name) {
34
- this.commands.push(name);
35
- },
36
- addEventListener() {},
37
- removeEventListener() {},
38
- focus() {
39
- this.focused = true;
40
- },
41
- blur() {
42
- this.blurred = true;
43
- },
44
- remove() {},
45
- });
46
-
47
- const make = (props = {}) => new Static({ latex: 'x=\\MathQuillMathField[r1]{}', getFieldName: () => {}, ...props });
48
-
49
- describe('Static: answer blocks / prompts', () => {
50
- describe('isInteractive', () => {
51
- it('is interactive when the latex has answer blocks', () => {
52
- expect(make({ latex: 'x=\\MathQuillMathField[r1]{}' }).isInteractive()).toBe(true);
53
- expect(make({ latex: 'x=\\placeholder[r1]{}' }).isInteractive()).toBe(true);
54
- });
55
-
56
- it('is display-only for plain latex', () => {
57
- expect(make({ latex: '\\frac{1}{2}' }).isInteractive()).toBe(false);
58
- expect(make({ latex: '' }).isInteractive()).toBe(false);
59
- });
60
- });
61
-
62
- describe('values', () => {
63
- it('returns {} before the field exists', () => {
64
- expect(make().values()).toEqual({});
65
- });
66
-
67
- it('maps each prompt id to its stored-form latex', () => {
68
- const s = make();
69
-
70
- s.mathField = stubField({ r1: '7', r2: '\\placeholder{}' });
71
-
72
- // \placeholder must not leak into stored values
73
- expect(s.values()).toEqual({ r1: '7', r2: '' });
74
- });
75
- });
76
-
77
- describe('promptHandle / innerFields', () => {
78
- it('exposes a MathQuill-field-like handle with get/set latex', () => {
79
- const s = make();
80
-
81
- s.mathField = stubField({ r1: '7' });
82
-
83
- const h = s.promptHandle('r1');
84
-
85
- expect(h.id).toEqual('r1');
86
- expect(h.latex()).toEqual('7');
87
-
88
- h.latex('9');
89
- expect(s.mathField.values.r1).toEqual('9');
90
- });
91
-
92
- it('handle latex() is safe with no field', () => {
93
- expect(make().promptHandle('r1').latex()).toEqual('');
94
- });
95
-
96
- it('innerFields returns one handle per prompt', () => {
97
- const s = make();
98
-
99
- s.mathField = stubField({ r1: '1', r2: '2' });
100
-
101
- expect(s.innerFields.map((f) => f.id)).toEqual(['r1', 'r2']);
102
- expect(make().innerFields).toEqual([]);
103
- });
104
- });
105
-
106
- describe('onSubFieldChange', () => {
107
- it('fires only for prompts whose value changed', () => {
108
- const onSubFieldChange = jest.fn();
109
- const s = make({ onSubFieldChange });
110
-
111
- s.mathField = stubField({ r1: '1', r2: '2' });
112
- s.promptValues = { r1: '1', r2: '2' };
113
-
114
- s.mathField.values.r2 = '5';
115
- s.onPromptInput();
116
-
117
- expect(onSubFieldChange).toHaveBeenCalledTimes(1);
118
- expect(onSubFieldChange).toHaveBeenCalledWith('r2', '5');
119
- });
120
-
121
- it('uses getFieldName when it returns a name', () => {
122
- const onSubFieldChange = jest.fn();
123
- const s = make({ onSubFieldChange, getFieldName: (handle) => 'named-' + handle.id });
124
-
125
- s.mathField = stubField({ r1: '1' });
126
- s.promptValues = {};
127
-
128
- s.onPromptInput();
129
-
130
- expect(onSubFieldChange).toHaveBeenCalledWith('named-r1', '1');
131
- });
132
-
133
- it('still tracks values when no callback is supplied', () => {
134
- const s = make();
135
-
136
- s.mathField = stubField({ r1: '3' });
137
- s.onPromptInput();
138
-
139
- expect(s.promptValues).toEqual({ r1: '3' });
140
- });
141
- });
142
-
143
- describe('onSubFieldFocus', () => {
144
- it('reports the prompt containing the selection, plus setInput', () => {
145
- const onSubFieldFocus = jest.fn();
146
- const setInput = jest.fn();
147
- const s = make({ onSubFieldFocus, setInput });
148
-
149
- // r2 occupies offsets 10..15
150
- s.mathField = stubField({ r1: '', r2: '' }, { ranges: [[11, 11]] });
151
- s.onPromptFocus();
152
-
153
- expect(onSubFieldFocus).toHaveBeenCalledTimes(1);
154
- expect(onSubFieldFocus.mock.calls[0][0]).toEqual('r2');
155
- expect(onSubFieldFocus.mock.calls[0][1].id).toEqual('r2');
156
- expect(setInput).toHaveBeenCalled();
157
- });
158
-
159
- it('does nothing without a selection', () => {
160
- const onSubFieldFocus = jest.fn();
161
- const s = make({ onSubFieldFocus });
162
-
163
- s.mathField = stubField({ r1: '' }, null);
164
- s.onPromptFocus();
165
-
166
- expect(onSubFieldFocus).not.toHaveBeenCalled();
167
- });
168
-
169
- it('does not throw when the field is missing', () => {
170
- const s = make({ onSubFieldFocus: jest.fn() });
171
-
172
- expect(() => s.onPromptFocus()).not.toThrow();
173
- });
174
- });
175
-
176
- describe('accessibility announcements', () => {
177
- afterEach(() => {
178
- document.body.innerHTML = '';
179
- });
180
-
181
- it('creates a polite off-screen live region', () => {
182
- const s = make();
183
-
184
- s.createLiveRegion();
185
-
186
- expect(s.liveRegion.getAttribute('aria-live')).toEqual('polite');
187
- expect(s.liveRegion.getAttribute('aria-atomic')).toEqual('true');
188
- expect(document.body.contains(s.liveRegion)).toBe(true);
189
-
190
- s.removeLiveRegion();
191
- expect(s.liveRegion).toBeUndefined();
192
- });
193
-
194
- it('does not create a second region', () => {
195
- const s = make();
196
-
197
- s.createLiveRegion();
198
- const first = s.liveRegion;
199
-
200
- s.createLiveRegion();
201
- expect(s.liveRegion).toBe(first);
202
- s.removeLiveRegion();
203
- });
204
-
205
- it('announces when typed input expands into a command', () => {
206
- const s = make();
207
-
208
- s.createLiveRegion();
209
- s.promptValues = { r1: 'x' };
210
- // grew by more than one char AND gained a backslash => a conversion
211
- s.announceConversion({ r1: 'x\\frac{}{}' });
212
-
213
- expect(s.liveRegion.textContent).toEqual('Converted to math symbol');
214
- s.removeLiveRegion();
215
- });
216
-
217
- it('does not announce ordinary typing', () => {
218
- const s = make();
219
-
220
- s.createLiveRegion();
221
- s.promptValues = { r1: 'x' };
222
- s.announceConversion({ r1: 'xy' });
223
-
224
- expect(s.liveRegion.textContent).toEqual('');
225
- s.removeLiveRegion();
226
- });
227
-
228
- it('does not announce a deletion', () => {
229
- const s = make();
230
-
231
- s.createLiveRegion();
232
- s.promptValues = { r1: 'x\\frac{}{}' };
233
- s.onKeyDown({ key: 'Backspace' });
234
- s.announceConversion({ r1: 'x' });
235
-
236
- expect(s.liveRegion.textContent).toEqual('');
237
- s.removeLiveRegion();
238
- });
239
- });
240
-
241
- describe('focus / blur', () => {
242
- it('delegates to the field and is safe without one', () => {
243
- const s = make();
244
-
245
- expect(() => {
246
- s.focus();
247
- s.blur();
248
- }).not.toThrow();
249
-
250
- s.mathField = stubField();
251
- s.focus();
252
- s.blur();
253
-
254
- expect(s.mathField.focused).toBe(true);
255
- expect(s.mathField.blurred).toBe(true);
256
- });
257
- });
258
-
259
- /**
260
- * MathLive shows the caret only while the field carries `ML__focused`, which
261
- * it adds during a render gated on `isSelectionEditable && hasFocus()`. In a
262
- * read-only field only prompt interiors are editable, so after a bare
263
- * `focus()` - whose selection sits outside every prompt - the answer block
264
- * looked focused but had no blinking cursor. Verified in Chromium: the caret
265
- * span goes from visibility:hidden to visible once the selection is inside a
266
- * prompt AND a render has run.
267
- */
268
- describe('caret placement', () => {
269
- it('puts a collapsed caret in the first prompt on focus', () => {
270
- const s = make();
271
-
272
- s.mathField = stubField({ r1: '', r2: '' });
273
- s.focus();
274
-
275
- // stub ranges are [0,5] for r1, [10,15] for r2
276
- expect(s.mathField.selection).toEqual({ ranges: [[0, 0]] });
277
- });
278
-
279
- it('forces the render that makes the caret visible', () => {
280
- const s = make();
281
-
282
- s.mathField = stubField({ r1: '' });
283
- s.focus();
284
-
285
- // assigning `selection` alone updates the model without re-rendering, so
286
- // ML__focused would never be added
287
- expect(s.mathField.commands).toEqual(['scrollIntoView']);
288
- });
289
-
290
- it('does not move a caret that is already inside a prompt', () => {
291
- const s = make();
292
-
293
- s.mathField = stubField({ r1: '', r2: '' });
294
- s.mathField.position = 12; // inside r2
295
- s.focus();
296
-
297
- expect(s.mathField.selection).toBe(null);
298
- expect(s.mathField.commands).toEqual([]);
299
- });
300
-
301
- it('targets a specific prompt', () => {
302
- const s = make();
303
-
304
- s.mathField = stubField({ r1: '', r2: '' });
305
- s.focusPrompt('r2');
306
-
307
- expect(s.mathField.selection).toEqual({ ranges: [[10, 10]] });
308
- });
309
-
310
- it('falls back to the first prompt for an unknown id', () => {
311
- const s = make();
312
-
313
- s.mathField = stubField({ r1: '', r2: '' });
314
- s.focusPrompt('nope');
315
-
316
- expect(s.mathField.selection).toEqual({ ranges: [[0, 0]] });
317
- });
318
-
319
- it('is a no-op when there are no prompts', () => {
320
- const s = make();
321
-
322
- s.mathField = stubField({});
323
-
324
- expect(() => s.focusPrompt()).not.toThrow();
325
- expect(s.mathField.selection).toBe(null);
326
- });
327
-
328
- it('is safe without a field', () => {
329
- expect(() => make().focusPrompt('r1')).not.toThrow();
330
- });
331
-
332
- describe('promptContaining', () => {
333
- it('finds the prompt whose range holds the offset, inclusive', () => {
334
- const s = make();
335
-
336
- s.mathField = stubField({ r1: '', r2: '' });
337
-
338
- expect(s.promptContaining(0)).toEqual('r1');
339
- expect(s.promptContaining(5)).toEqual('r1');
340
- expect(s.promptContaining(10)).toEqual('r2');
341
- expect(s.promptContaining(7)).toBeUndefined();
342
- });
343
-
344
- it('treats a missing position as outside', () => {
345
- const s = make();
346
-
347
- s.mathField = stubField({ r1: '' });
348
-
349
- expect(s.promptContaining(undefined)).toBeUndefined();
350
- });
351
- });
352
-
353
- // MathQuill's inner fields could be focused directly.
354
- it('promptHandle exposes focus() for its own prompt', () => {
355
- const s = make();
356
-
357
- s.mathField = stubField({ r1: '', r2: '' });
358
- s.promptHandle('r2').focus();
359
-
360
- expect(s.mathField.focused).toBe(true);
361
- expect(s.mathField.selection).toEqual({ ranges: [[10, 10]] });
362
- });
363
- });
364
- });
@@ -1,73 +0,0 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import { keysForGrade, normalizeAdditionalKeys } from './keys/grades';
4
- import { extendKeySet } from './keys/utils';
5
- import Keypad from './keypad';
6
-
7
- /**
8
- * Maps a key definition to the legacy `{value, type}` shape that
9
- * @pie-lib/math-toolbar consumes. Unchanged from the MathQuill implementation:
10
- * the shape is part of the 4.x-compatible contract, and translation to MathLive
11
- * happens later, in `latex-bridge.keyToAction`.
12
- */
13
- const toOldModel = (d) => {
14
- if (d.command) {
15
- return { value: d.command, type: 'command' };
16
- } else if (d.write) {
17
- return { value: d.write };
18
- } else if (d.keystroke) {
19
- return { type: 'cursor', value: d.keystroke };
20
- }
21
- };
22
-
23
- export default class HorizontalKeypad extends React.Component {
24
- static propTypes = {
25
- className: PropTypes.string,
26
- controlledKeypadMode: PropTypes.bool,
27
- mode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
28
- layoutForKeyPad: PropTypes.object,
29
- onClick: PropTypes.func.isRequired,
30
- onFocus: PropTypes.func,
31
- noDecimal: PropTypes.bool,
32
- additionalKeys: PropTypes.array,
33
- setKeypadInteraction: PropTypes.func,
34
- };
35
-
36
- static defaultProps = {
37
- mode: 'scientific',
38
- noDecimal: false,
39
- additionalKeys: [],
40
- };
41
-
42
- keypadPress = (data) => {
43
- this.props.onClick(toOldModel(data));
44
- };
45
-
46
- render() {
47
- const {
48
- mode,
49
- onFocus,
50
- controlledKeypadMode,
51
- noDecimal,
52
- className,
53
- additionalKeys,
54
- layoutForKeyPad,
55
- setKeypadInteraction,
56
- } = this.props;
57
- const normalizedKeys = normalizeAdditionalKeys(additionalKeys);
58
-
59
- return (
60
- <Keypad
61
- className={className}
62
- controlledKeypadMode={controlledKeypadMode}
63
- onFocus={onFocus}
64
- noDecimal={noDecimal}
65
- layoutForKeyPad={layoutForKeyPad}
66
- additionalKeys={extendKeySet(keysForGrade(mode), normalizedKeys)}
67
- onPress={this.keypadPress}
68
- mode={mode}
69
- setKeypadInteraction={setKeypadInteraction}
70
- />
71
- );
72
- }
73
- }
package/src/index.jsx DELETED
@@ -1,63 +0,0 @@
1
- import { keysForGrade } from './keys/grades';
2
- import * as keys from './keys';
3
-
4
- import MathInput from './math-input';
5
- import HorizontalKeypad from './horizontal-keypad';
6
- import KeyPad from './keypad';
7
-
8
- import * as mf from './mf';
9
- import {
10
- registerEmbed,
11
- applyStaticMath,
12
- latexToMarkup,
13
- loadMathLive,
14
- configureFonts,
15
- getMacros,
16
- PIE_MACROS,
17
- } from './mathlive-instance';
18
- import { toMathLive, fromMathLive, keyToAction, NEWLINE_EMBED } from './latex-bridge';
19
-
20
- const addLeftBracket = (s) => (s.indexOf('\\(') === 0 ? s : `\\(${s}`);
21
- const addRightBracket = (s) => (s.indexOf('\\)') === s.length - 2 ? s : `${s}\\)`);
22
- const rmLeftBracket = (s) => (s.indexOf('\\(') === 0 ? s.substring(2) : s);
23
- const rmRightBracket = (s) => (s.indexOf('\\)') === s.length - 2 ? s.substring(0, s.length - 2) : s);
24
-
25
- const addBrackets = (s) => addRightBracket(addLeftBracket(s));
26
- const removeBrackets = (s) => rmRightBracket(rmLeftBracket(s));
27
-
28
- /**
29
- * `updateSpans` is intentionally a no-op.
30
- *
31
- * In @pie-lib/math-input it patched MathQuill's rendered DOM by walking
32
- * `span[mathquill-command-id]` to resize the parallel glyph and tag primes.
33
- * MathLive emits none of those attributes and does not need the fixups, but the
34
- * export is kept so callers do not break.
35
- */
36
- const updateSpans = () => {};
37
-
38
- export {
39
- keysForGrade,
40
- addBrackets,
41
- removeBrackets,
42
- keys,
43
- MathInput,
44
- HorizontalKeypad,
45
- KeyPad,
46
- updateSpans,
47
- // `mf` is the MathLive equivalent of math-input's `mq`. Both names are
48
- // exported so a consumer can switch packages by changing only the import.
49
- mf,
50
- mf as mq,
51
- registerEmbed,
52
- applyStaticMath,
53
- // MathLive-specific additions
54
- latexToMarkup,
55
- loadMathLive,
56
- configureFonts,
57
- getMacros,
58
- PIE_MACROS,
59
- toMathLive,
60
- fromMathLive,
61
- keyToAction,
62
- NEWLINE_EMBED,
63
- };