@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
package/src/mf/static.jsx DELETED
@@ -1,479 +0,0 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import { styled } from '@mui/material/styles';
4
- import debug from 'debug';
5
- import {
6
- loadMathLive,
7
- getMacros,
8
- latexToMarkup,
9
- MATH_MODE_SPACE,
10
- applyShadowStyles,
11
- trackPlaceholderCaret,
12
- } from '../mathlive-instance';
13
- import { toMathLive, fromMathLive, fieldIds } from '../latex-bridge';
14
- import { placeholderStyles, rootIndexStyles, parallelStyles } from './common-styles';
15
-
16
- const log = debug('pie-lib:math-input-mathlive:static');
17
-
18
- const Holder = styled('span')({
19
- display: 'inline-block',
20
- // Shared static-math rules: muted empty-slot glyphs and svg containment.
21
- ...placeholderStyles,
22
- // Readable radical index - display mode renders in the light DOM, so the
23
- // shadow-root copy of this rule does not reach it.
24
- ...rootIndexStyles,
25
- ...parallelStyles,
26
- '& math-field': {
27
- display: 'inline-block',
28
- border: 'none',
29
- background: 'transparent',
30
- },
31
- '& math-field::part(virtual-keyboard-toggle)': {
32
- display: 'none',
33
- },
34
- '& math-field::part(menu-toggle)': {
35
- display: 'none',
36
- },
37
- });
38
-
39
- /**
40
- * Static math. Drop-in replacement for @pie-lib/math-input's `mq.Static`.
41
- *
42
- * Two modes, chosen automatically:
43
- *
44
- * - **display** (no answer blocks in the latex): rendered with
45
- * `convertLatexToMarkup`. No mathfield instance, so it is cheap enough for
46
- * keypad labels - the old implementation created a full MathQuill
47
- * StaticMath per button.
48
- *
49
- * - **interactive** (latex contains `\MathQuillMathField[..]{}` /
50
- * `\placeholder[..]{}`): a read-only `<math-field>` whose placeholders stay
51
- * editable. Sub-field values are surfaced through the prompts API, replacing
52
- * MathQuill's `innerFields`.
53
- */
54
- export default class Static extends React.Component {
55
- static propTypes = {
56
- latex: PropTypes.string.isRequired,
57
- className: PropTypes.string,
58
- onFocus: PropTypes.func,
59
- onBlur: PropTypes.func,
60
- getFieldName: PropTypes.func,
61
- onSubFieldChange: PropTypes.func,
62
- onSubFieldFocus: PropTypes.func,
63
- setInput: PropTypes.func,
64
- };
65
-
66
- static defaultProps = {
67
- getFieldName: () => {},
68
- };
69
-
70
- constructor(props) {
71
- super(props);
72
- this.holderRef = React.createRef();
73
- this.state = { markup: '' };
74
- this.promptValues = {};
75
- }
76
-
77
- async componentDidMount() {
78
- // See the note in mf/input.jsx: a generation counter, not a boolean, so a
79
- // StrictMode mount -> unmount -> mount on the same instance still ends up
80
- // with exactly one field.
81
- const generation = (this.mountGeneration = (this.mountGeneration || 0) + 1);
82
-
83
- if (!this.isInteractive()) {
84
- await loadMathLive();
85
-
86
- if (generation === this.mountGeneration) {
87
- this.renderMarkup();
88
- }
89
-
90
- return;
91
- }
92
-
93
- const ml = await loadMathLive();
94
-
95
- if (generation !== this.mountGeneration || !ml || !this.holderRef.current || this.mathField) {
96
- return;
97
- }
98
-
99
- this.mathField = new ml.MathfieldElement({ macros: getMacros() });
100
- this.mathField.readOnly = true;
101
- this.mathField.mathVirtualKeyboardPolicy = 'manual';
102
- // Without this the spacebar does nothing: MathLive's mathModeSpace
103
- // defaults to an empty string.
104
- this.mathField.mathModeSpace = MATH_MODE_SPACE;
105
- // Same as the editor: stay in the exponent after a digit (see mf/input.jsx).
106
- this.mathField.smartSuperscript = false;
107
- this.mathField.value = toMathLive(this.props.latex);
108
-
109
- // The mathfield renders in shadow DOM, so page CSS cannot reach it - the
110
- // stretchy-accent fix has to be injected there directly.
111
- applyShadowStyles(this.mathField);
112
-
113
- // A selected `\placeholder{}` gets no MathLive caret; draw one. Answer
114
- // blocks are typed into with the same keypad, so they grow placeholders too.
115
- this.untrackCaret = trackPlaceholderCaret(this.mathField);
116
-
117
- this.mathField.addEventListener('input', this.onPromptInput);
118
- this.mathField.addEventListener('focusin', this.onPromptFocus);
119
- this.mathField.addEventListener('keydown', this.onKeyDown);
120
-
121
- // Belt and braces: drop anything a previous mount may have left behind.
122
- this.holderRef.current.replaceChildren(this.mathField);
123
-
124
- this.createLiveRegion();
125
- }
126
-
127
- componentDidUpdate(prevProps) {
128
- if (prevProps.latex === this.props.latex) {
129
- return;
130
- }
131
-
132
- if (this.mathField) {
133
- this.mathField.value = toMathLive(this.props.latex);
134
- } else {
135
- this.renderMarkup();
136
- }
137
- }
138
-
139
- componentWillUnmount() {
140
- // Invalidate any mount still awaiting the MathLive load.
141
- this.mountGeneration = (this.mountGeneration || 0) + 1;
142
-
143
- if (this.untrackCaret) {
144
- this.untrackCaret();
145
- this.untrackCaret = undefined;
146
- }
147
-
148
- if (this.mathField) {
149
- this.mathField.removeEventListener('input', this.onPromptInput);
150
- this.mathField.removeEventListener('focusin', this.onPromptFocus);
151
- this.mathField.removeEventListener('keydown', this.onKeyDown);
152
- this.mathField.remove();
153
- this.mathField = undefined;
154
- }
155
-
156
- this.removeLiveRegion();
157
- }
158
-
159
- /**
160
- * Off-screen aria-live region, ported from @pie-lib/math-input's Static.
161
- *
162
- * MathLive has its own accessibility layer, but it does not announce that
163
- * typed input was converted into a math symbol, which screen-reader users
164
- * relied on in the MathQuill implementation.
165
- */
166
- createLiveRegion = () => {
167
- if (typeof document === 'undefined' || this.liveRegion) {
168
- return;
169
- }
170
-
171
- this.liveRegion = document.createElement('div');
172
- Object.assign(this.liveRegion.style, {
173
- position: 'absolute',
174
- width: '1px',
175
- height: '1px',
176
- marginTop: '-1px',
177
- clip: 'rect(1px, 1px, 1px, 1px)',
178
- overflow: 'hidden',
179
- });
180
- this.liveRegion.setAttribute('aria-live', 'polite');
181
- this.liveRegion.setAttribute('aria-atomic', 'true');
182
-
183
- document.body.appendChild(this.liveRegion);
184
- };
185
-
186
- removeLiveRegion = () => {
187
- if (this.liveRegion && this.liveRegion.parentNode) {
188
- this.liveRegion.parentNode.removeChild(this.liveRegion);
189
- }
190
-
191
- this.liveRegion = undefined;
192
- };
193
-
194
- announce = (message) => {
195
- if (!this.liveRegion) {
196
- return;
197
- }
198
-
199
- this.liveRegion.textContent = message;
200
-
201
- // Clear once announced, so the same message can be announced again.
202
- clearTimeout(this.announceTimeout);
203
- this.announceTimeout = setTimeout(() => {
204
- if (this.liveRegion) {
205
- this.liveRegion.textContent = '';
206
- }
207
- }, 500);
208
- };
209
-
210
- onKeyDown = (event) => {
211
- // Deletions should not be announced as conversions.
212
- this.isDeleting = event?.key === 'Backspace' || event?.key === 'Delete';
213
- };
214
-
215
- /**
216
- * Announce when typed input became a math symbol. The MathQuill version
217
- * inferred this by counting braces; here a prompt whose latex grew by more
218
- * than the single character typed means a command was expanded.
219
- */
220
- announceConversion = (next) => {
221
- if (this.isDeleting) {
222
- this.isDeleting = false;
223
- return;
224
- }
225
-
226
- const converted = Object.keys(next).some((id) => {
227
- const before = this.promptValues[id] || '';
228
- const after = next[id] || '';
229
-
230
- return after.length > before.length + 1 && /\\/.test(after.slice(before.length));
231
- });
232
-
233
- if (converted) {
234
- this.announce('Converted to math symbol');
235
- }
236
- };
237
-
238
- /** Does this latex contain answer blocks? */
239
- isInteractive() {
240
- const latex = this.props.latex || '';
241
-
242
- return fieldIds(latex).length > 0 || latex.indexOf('\\placeholder') !== -1;
243
- }
244
-
245
- renderMarkup() {
246
- const markup = latexToMarkup(this.props.latex);
247
-
248
- if (markup !== this.state.markup) {
249
- this.setState({ markup });
250
- }
251
- }
252
-
253
- /** MathQuill's `innerFields` equivalent, keyed by prompt id. */
254
- values() {
255
- if (!this.mathField) {
256
- return {};
257
- }
258
-
259
- const out = {};
260
-
261
- (this.mathField.getPrompts() || []).forEach((id) => {
262
- out[id] = fromMathLive(this.mathField.getPromptValue(id));
263
- });
264
-
265
- return out;
266
- }
267
-
268
- /**
269
- * A MathQuill-inner-field-like handle for a prompt.
270
- *
271
- * The old API handed callers a MathQuill field object, and consumers commonly
272
- * called `field.latex()` on it. There is no equivalent object in MathLive -
273
- * prompts are addressed by id - so this adapter exposes the parts that were
274
- * actually used: `id` and a get/set `latex()`.
275
- */
276
- promptHandle(id) {
277
- return {
278
- id,
279
- // MathQuill's inner fields could be focused directly; keep that.
280
- focus: () => {
281
- if (this.mathField) {
282
- this.mathField.focus();
283
- this.focusPrompt(id);
284
- }
285
- },
286
- latex: (value) => {
287
- if (!this.mathField) {
288
- return '';
289
- }
290
-
291
- if (value === undefined) {
292
- return fromMathLive(this.mathField.getPromptValue(id));
293
- }
294
-
295
- this.mathField.setPromptValue(id, toMathLive(value), {});
296
-
297
- return value;
298
- },
299
- };
300
- }
301
-
302
- /** All prompt handles, the analogue of `mathField.innerFields`. */
303
- get innerFields() {
304
- if (!this.mathField) {
305
- return [];
306
- }
307
-
308
- return (this.mathField.getPrompts() || []).map((id) => this.promptHandle(id));
309
- }
310
-
311
- onPromptInput = () => {
312
- const { onSubFieldChange, getFieldName } = this.props;
313
-
314
- if (!this.mathField) {
315
- return;
316
- }
317
-
318
- const next = this.values();
319
-
320
- this.announceConversion(next);
321
-
322
- if (!onSubFieldChange) {
323
- this.promptValues = next;
324
- return;
325
- }
326
-
327
- const fields = this.innerFields;
328
-
329
- // Emit only the prompts whose value actually changed, mirroring the
330
- // per-field granularity of the old onSubFieldChange(name, latex).
331
- Object.keys(next).forEach((id) => {
332
- if (next[id] === this.promptValues[id]) {
333
- return;
334
- }
335
-
336
- const name = getFieldName(this.promptHandle(id), fields) || id;
337
-
338
- onSubFieldChange(name, next[id]);
339
- });
340
-
341
- this.promptValues = next;
342
- };
343
-
344
- onPromptFocus = () => {
345
- const { onSubFieldFocus, getFieldName } = this.props;
346
-
347
- if (!this.mathField || !onSubFieldFocus) {
348
- return;
349
- }
350
-
351
- try {
352
- const ids = this.mathField.getPrompts() || [];
353
- // The focused prompt is the one containing the current selection.
354
- const focused = ids.find((id) => {
355
- const range = this.mathField.getPromptRange(id);
356
- const selection = this.mathField.selection;
357
-
358
- if (!range || !selection || !selection.ranges || !selection.ranges.length) {
359
- return false;
360
- }
361
-
362
- const [start, end] = range;
363
- const [selStart] = selection.ranges[0];
364
-
365
- return selStart >= start && selStart <= end;
366
- });
367
-
368
- if (focused) {
369
- const handle = this.promptHandle(focused);
370
- const name = getFieldName(handle, this.innerFields) || focused;
371
-
372
- if (this.props.setInput) {
373
- this.props.setInput(handle);
374
- }
375
-
376
- onSubFieldFocus(name, handle);
377
- }
378
- } catch (e) {
379
- log('error resolving focused prompt: %s', e.message);
380
- }
381
- };
382
-
383
- /** The id of the prompt whose range contains `position`, if any. */
384
- promptContaining(position) {
385
- if (!this.mathField || typeof position !== 'number') {
386
- return undefined;
387
- }
388
-
389
- return (this.mathField.getPrompts() || []).find((id) => {
390
- const range = this.mathField.getPromptRange(id);
391
-
392
- return range && position >= range[0] && position <= range[1];
393
- });
394
- }
395
-
396
- /**
397
- * Put a blinking caret inside an answer block.
398
- *
399
- * MathLive only makes the caret visible while the field element carries the
400
- * `ML__focused` class, and it adds that class during a *render*, gated on
401
- * `isSelectionEditable && hasFocus()`. In a read-only field only prompt
402
- * interiors are editable, so the two things both have to be true:
403
- *
404
- * - the selection must sit inside a prompt, and
405
- * - a render must run afterwards.
406
- *
407
- * Assigning `selection` satisfies the first but not the second - it updates
408
- * the model without re-rendering, so the class stays off and the caret span
409
- * remains `visibility: hidden`. Executing any command does trigger the
410
- * render, and `scrollIntoView` is the one with no other effect: it keeps the
411
- * caret on screen, which the preceding `focus()` already does anyway.
412
- *
413
- * @param {string} [id] prompt to focus; defaults to the first one
414
- */
415
- focusPrompt(id) {
416
- if (!this.mathField) {
417
- return;
418
- }
419
-
420
- const ids = this.mathField.getPrompts() || [];
421
- const target = id && ids.indexOf(id) !== -1 ? id : ids[0];
422
-
423
- if (!target) {
424
- return;
425
- }
426
-
427
- const range = this.mathField.getPromptRange(target);
428
-
429
- if (!range) {
430
- return;
431
- }
432
-
433
- this.mathField.selection = { ranges: [[range[0], range[0]]] };
434
-
435
- if (typeof this.mathField.executeCommand === 'function') {
436
- this.mathField.executeCommand('scrollIntoView');
437
- }
438
- }
439
-
440
- focus() {
441
- if (!this.mathField) {
442
- return;
443
- }
444
-
445
- this.mathField.focus();
446
-
447
- // A bare focus() leaves the selection outside every prompt, which in a
448
- // read-only field is not editable - the block looked focused but had no
449
- // cursor. Clicking a prompt already works, because MathLive's own pointer
450
- // handling moves the selection and re-renders.
451
- if (!this.promptContaining(this.mathField.position)) {
452
- this.focusPrompt();
453
- }
454
- }
455
-
456
- blur() {
457
- this.mathField && this.mathField.blur();
458
- }
459
-
460
- render() {
461
- const { className, onFocus, onBlur } = this.props;
462
- const { markup } = this.state;
463
-
464
- if (this.mathField || this.isInteractive()) {
465
- return <Holder className={className} onFocus={onFocus} onBlur={onBlur} ref={this.holderRef} />;
466
- }
467
-
468
- return (
469
- <Holder
470
- className={className}
471
- onFocus={onFocus}
472
- onBlur={onBlur}
473
- ref={this.holderRef}
474
- // eslint-disable-next-line react/no-danger
475
- dangerouslySetInnerHTML={{ __html: markup }}
476
- />
477
- );
478
- }
479
- }
package/src/selection.js DELETED
@@ -1,146 +0,0 @@
1
- import debug from 'debug';
2
-
3
- const log = debug('pie-lib:math-input-mathlive:selection');
4
-
5
- /**
6
- * Widen a selection so it covers whole atoms of a single parent.
7
- *
8
- * MathLive extends a selection over *flat offsets*, so Shift+Arrow walks into
9
- * a fraction instead of over it: from before `2x` in `5x^2+2x+\frac{\pi}{2}-4`,
10
- * five presses select `2x+` plus the `\pi` of the numerator, and copying gives
11
- * `2x+\pi`. MathQuill treated a fraction (root, superscript...) as one unit,
12
- * which is what authors expect.
13
- *
14
- * The tree is recovered from MathLive's public `getElementInfo(offset)`, where
15
- * offset `i` describes the atom ending at caret position `i`:
16
- *
17
- * - a composite atom (fraction, root, script) owns the offset right *after*
18
- * its children; the children are one level deeper;
19
- * - each branch (numerator, denominator, radicand) opens with an empty-latex
20
- * "start" atom.
21
- *
22
- * The rule: find the shallowest depth the selection touches, counting the caret
23
- * it starts from. If the range crosses into a sibling branch at that depth, the
24
- * real common parent is one level up. Then push both ends out of anything
25
- * deeper. A script atom (`^2`) is kept with its base, since MathLive stores
26
- * `x^2` as `x` followed by a separate `^2` atom.
27
- *
28
- * Pure, so it can be tested without a browser.
29
- *
30
- * @param {number} start
31
- * @param {number} end
32
- * @param {function(number): {depth?: number, latex?: string}} info
33
- * @param {number} lastOffset
34
- * @returns {[number, number]} the widened range, start <= end
35
- */
36
- export const expandRange = (start, end, info, lastOffset) => {
37
- const at = (i) => info(i) || {};
38
- const depth = (i) => at(i).depth || 0;
39
- const isBranchStart = (i) => depth(i) > 0 && at(i).latex === '';
40
-
41
- if (end < start) {
42
- [start, end] = [end, start];
43
- }
44
-
45
- if (end - start < 1) {
46
- return [start, end];
47
- }
48
-
49
- let target = Infinity;
50
-
51
- for (let i = start; i <= end; i++) {
52
- target = Math.min(target, depth(i));
53
- }
54
-
55
- for (let i = start + 1; i <= end; i++) {
56
- if (depth(i) === target && isBranchStart(i)) {
57
- target -= 1;
58
- break;
59
- }
60
- }
61
-
62
- target = Math.max(target, 0);
63
-
64
- while (start > 0 && depth(start) > target) {
65
- start -= 1;
66
- }
67
-
68
- while (end < lastOffset && depth(end) > target) {
69
- end += 1;
70
- }
71
-
72
- // Keep a superscript / subscript with its base.
73
- let first = start + 1;
74
-
75
- while (first <= end && depth(first) > target) {
76
- first += 1;
77
- }
78
-
79
- if (start > 0 && first <= end && /^[\^_]/.test(at(first).latex || '') && depth(start) === target) {
80
- start -= 1;
81
- }
82
-
83
- return [start, end];
84
- };
85
-
86
- /**
87
- * Keep a mathfield's selection snapped to whole atoms (see {@link expandRange}).
88
- *
89
- * This fixes both what the user sees highlighted and what copy/cut put on the
90
- * clipboard, since both come from the selection. The selection direction is
91
- * preserved, so further Shift+Arrow presses keep extending the same end.
92
- *
93
- * @param {HTMLElement} mathField a MathLive `<math-field>`
94
- * @returns {function|undefined} teardown, to call on unmount
95
- */
96
- export const trackWholeAtomSelection = (mathField) => {
97
- if (!mathField || typeof mathField.addEventListener !== 'function') {
98
- return undefined;
99
- }
100
-
101
- let applying = false;
102
-
103
- const update = () => {
104
- if (applying) {
105
- return;
106
- }
107
-
108
- try {
109
- if (mathField.selectionIsCollapsed || typeof mathField.getElementInfo !== 'function') {
110
- return;
111
- }
112
-
113
- const selection = mathField.selection;
114
- const range = selection && selection.ranges && selection.ranges[0];
115
-
116
- if (!range) {
117
- return;
118
- }
119
-
120
- const [start, end] = range;
121
- const lo = Math.min(start, end);
122
- const hi = Math.max(start, end);
123
- const [nextStart, nextEnd] = expandRange(
124
- lo,
125
- hi,
126
- (i) => mathField.getElementInfo(i),
127
- mathField.lastOffset,
128
- );
129
-
130
- if (nextStart === lo && nextEnd === hi) {
131
- return;
132
- }
133
-
134
- applying = true;
135
- mathField.selection = { ranges: [[nextStart, nextEnd]], direction: selection.direction };
136
- } catch (e) {
137
- log('could not normalise the selection: %s', e && e.message);
138
- } finally {
139
- applying = false;
140
- }
141
- };
142
-
143
- mathField.addEventListener('selection-change', update);
144
-
145
- return () => mathField.removeEventListener('selection-change', update);
146
- };