@pie-lib/math-input-mathlive 1.0.9 → 1.0.11
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.
- package/README.md +2 -2
- package/lib/__tests__/keypad-label.test.js +75 -1
- package/lib/__tests__/keypad-label.test.js.map +1 -1
- package/lib/__tests__/latex-bridge.test.js +29 -0
- package/lib/__tests__/latex-bridge.test.js.map +1 -1
- package/lib/__tests__/mathlive-instance.test.js +42 -0
- package/lib/__tests__/mathlive-instance.test.js.map +1 -1
- package/lib/__tests__/mount-race.test.js +1 -0
- package/lib/__tests__/mount-race.test.js.map +1 -1
- package/lib/keypad/index.js +11 -13
- package/lib/keypad/index.js.map +1 -1
- package/lib/keys/geometry.js +7 -4
- package/lib/keys/geometry.js.map +1 -1
- package/lib/latex-bridge.js +18 -3
- package/lib/latex-bridge.js.map +1 -1
- package/lib/mathlive-instance.js +56 -1
- package/lib/mathlive-instance.js.map +1 -1
- package/lib/mf/common-styles.js +33 -4
- package/lib/mf/common-styles.js.map +1 -1
- package/lib/mf/input.js +4 -0
- package/lib/mf/input.js.map +1 -1
- package/lib/mf/static.js +4 -0
- package/lib/mf/static.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/keypad-label.test.jsx +76 -1
- package/src/__tests__/latex-bridge.test.js +21 -0
- package/src/__tests__/mathlive-instance.test.js +47 -0
- package/src/__tests__/mount-race.test.jsx +1 -0
- package/src/keypad/index.jsx +11 -13
- package/src/keys/geometry.js +7 -2
- package/src/latex-bridge.js +18 -3
- package/src/mathlive-instance.js +61 -0
- package/src/mf/common-styles.js +33 -4
- package/src/mf/input.jsx +5 -1
- package/src/mf/static.jsx +5 -1
package/src/latex-bridge.js
CHANGED
|
@@ -55,11 +55,19 @@ export const DEFAULT_FIELD_ID = 'r1';
|
|
|
55
55
|
*
|
|
56
56
|
* `\placeholder{}` is not usable here: in static markup it renders as a
|
|
57
57
|
* non-breaking space, because its editable box only exists inside a live
|
|
58
|
-
* mathfield. So
|
|
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.
|
|
59
67
|
*
|
|
60
68
|
* Live mathfields need none of this - MathLive manages empty slots itself.
|
|
61
69
|
*/
|
|
62
|
-
const EMPTY_SLOT = '\\htmlData{pie-empty=1}{\\
|
|
70
|
+
const EMPTY_SLOT = '\\htmlData{pie-empty=1}{\\rule{0.55em}{0.55em}}';
|
|
63
71
|
const EMPTY_GROUP_REGEX = /\{\s*\}/g;
|
|
64
72
|
|
|
65
73
|
/**
|
|
@@ -240,8 +248,15 @@ export const fromMathLive = (latex) => {
|
|
|
240
248
|
* placeholders in the inserted latex.
|
|
241
249
|
*/
|
|
242
250
|
const COMMANDS_WITH_ARGS = {
|
|
251
|
+
// An explicit "blank over blank" key: both slots start empty.
|
|
243
252
|
'\\frac': '\\frac{#?}{#?}',
|
|
244
|
-
'
|
|
253
|
+
// Division, which must behave like MathQuill's `/`: whatever precedes the
|
|
254
|
+
// cursor becomes the numerator and the caret lands in the denominator. `#@`
|
|
255
|
+
// is MathLive's token for "the selection, or the item before the cursor" -
|
|
256
|
+
// this is exactly the template MathLive binds to the `/` key itself. Using
|
|
257
|
+
// `#?` here instead produced an empty fraction and left the typed number
|
|
258
|
+
// stranded outside it.
|
|
259
|
+
'/': '\\frac{#@}{#?}',
|
|
245
260
|
'\\sqrt': '\\sqrt{#?}',
|
|
246
261
|
'\\nthroot': '\\sqrt[#?]{#?}',
|
|
247
262
|
'\\overline': '\\overline{#?}',
|
package/src/mathlive-instance.js
CHANGED
|
@@ -180,6 +180,67 @@ const configureLoaded = () => {
|
|
|
180
180
|
injectCoreStylesheet();
|
|
181
181
|
};
|
|
182
182
|
|
|
183
|
+
/**
|
|
184
|
+
* CSS that has to live *inside* a mathfield's shadow root.
|
|
185
|
+
*
|
|
186
|
+
* A live `<math-field>` renders in shadow DOM (`attachShadow({mode:'open'})`),
|
|
187
|
+
* so nothing in the host page's stylesheets reaches it. The stretchy-accent
|
|
188
|
+
* problem therefore needs fixing twice: once for static markup (see
|
|
189
|
+
* `placeholderStyles`) and once here for the editor.
|
|
190
|
+
*
|
|
191
|
+
* MathLive centres an accent with `margin-left: <half the content width>` as an
|
|
192
|
+
* inline style. For a stretchy accent that also shrinks its containing block
|
|
193
|
+
* (`.ML__center` is position:relative), so the arc ends up half-width and offset
|
|
194
|
+
* - visibly failing to span the text underneath.
|
|
195
|
+
*
|
|
196
|
+
* `:has(.ML__stretchy)` keeps the fix to stretchy accents; single-glyph accents
|
|
197
|
+
* (`\hat{x}`, `\vec{x}`) still need their centring offset.
|
|
198
|
+
*/
|
|
199
|
+
const SHADOW_CSS = '.ML__latex .ML__center:has(.ML__stretchy){margin-left:0 !important}';
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Apply {@link SHADOW_CSS} inside a mathfield's shadow root.
|
|
203
|
+
*
|
|
204
|
+
* Uses `adoptedStyleSheets` when available (which is how MathLive loads its own
|
|
205
|
+
* stylesheets) and falls back to appending a <style> node.
|
|
206
|
+
*
|
|
207
|
+
* @param {HTMLElement} mathField
|
|
208
|
+
*/
|
|
209
|
+
export const applyShadowStyles = (mathField) => {
|
|
210
|
+
const root = mathField && mathField.shadowRoot;
|
|
211
|
+
|
|
212
|
+
if (!root) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
if ('adoptedStyleSheets' in root && typeof CSSStyleSheet !== 'undefined') {
|
|
218
|
+
// Do not re-add on a re-render.
|
|
219
|
+
if (root.__pieAccentSheet) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const sheet = new CSSStyleSheet();
|
|
224
|
+
|
|
225
|
+
sheet.replaceSync(SHADOW_CSS);
|
|
226
|
+
root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
|
|
227
|
+
root.__pieAccentSheet = sheet;
|
|
228
|
+
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!root.querySelector('style[data-pie-accent]')) {
|
|
233
|
+
const style = document.createElement('style');
|
|
234
|
+
|
|
235
|
+
style.setAttribute('data-pie-accent', '');
|
|
236
|
+
style.textContent = SHADOW_CSS;
|
|
237
|
+
root.appendChild(style);
|
|
238
|
+
}
|
|
239
|
+
} catch (e) {
|
|
240
|
+
log('could not apply shadow styles to the mathfield: %s', e && e.message);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
183
244
|
/**
|
|
184
245
|
* Ensure MathLive's "core" stylesheet is in the document.
|
|
185
246
|
*
|
package/src/mf/common-styles.js
CHANGED
|
@@ -57,13 +57,42 @@ export const placeholderStyles = {
|
|
|
57
57
|
'& [data-pie-empty]': {
|
|
58
58
|
opacity: 0.5,
|
|
59
59
|
},
|
|
60
|
-
// Stretchy accents
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
60
|
+
// Stretchy accents (\overarc, \overleftrightarrow, \overrightarrow) render an
|
|
61
|
+
// <svg width="100%" preserveAspectRatio="none"> inside a `.ML__stretchy`. That
|
|
62
|
+
// span is absolutely positioned, so its immediate wrapper computes to 0px and
|
|
63
|
+
// the width has to resolve against a *positioned* ancestor. Pin that to
|
|
64
|
+
// `.ML__base`, whose width already equals the accent's content (measured:
|
|
65
|
+
// 9.85px for a 0.56em slot), so the arc spans exactly its content.
|
|
66
|
+
//
|
|
67
|
+
// Do NOT write a bare `& svg` rule instead: MathLive's own rule is
|
|
68
|
+
// descendant-scoped (`.ML__latex .ML__stretchy svg`), and a global one
|
|
69
|
+
// re-parents every svg in the subtree.
|
|
70
|
+
'& .ML__base': {
|
|
71
|
+
position: 'relative',
|
|
72
|
+
},
|
|
73
|
+
'& .ML__stretchy': {
|
|
64
74
|
position: 'absolute',
|
|
75
|
+
left: 0,
|
|
65
76
|
width: '100%',
|
|
66
77
|
},
|
|
78
|
+
// MathLive centres an accent by shifting it right half the content width -
|
|
79
|
+
// `margin-left: 0.28em` for a 0.56em slot - which assumes a glyph with its own
|
|
80
|
+
// side bearings. Over a synthetic empty slot it just pushes the arc right.
|
|
81
|
+
//
|
|
82
|
+
// `!important` is required: MathLive writes this as an INLINE style
|
|
83
|
+
// (`style="top:-3.4em;margin-left:0.28em"`), which a class cannot override.
|
|
84
|
+
//
|
|
85
|
+
// It also fixes the width. `.ML__center` is the stretchy's containing block
|
|
86
|
+
// (MathLive gives `.ML__vlist > span` position:relative), and its width is the
|
|
87
|
+
// base minus that margin - measured 9.85px - 4.93px = 4.93px, exactly half,
|
|
88
|
+
// which is why the arc came out half-width AND offset right.
|
|
89
|
+
//
|
|
90
|
+
// Scoped with `:has(.ML__stretchy)`: only stretchy accents are affected.
|
|
91
|
+
// A single-glyph accent (`\hat{x}`, `\vec{x}`) genuinely needs its centring
|
|
92
|
+
// offset, and must keep it.
|
|
93
|
+
'& .ML__center:has(.ML__stretchy)': {
|
|
94
|
+
marginLeft: '0 !important',
|
|
95
|
+
},
|
|
67
96
|
};
|
|
68
97
|
|
|
69
98
|
/**
|
package/src/mf/input.jsx
CHANGED
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { styled } from '@mui/material/styles';
|
|
4
4
|
import debug from 'debug';
|
|
5
|
-
import { loadMathLive, getMacros, MATH_MODE_SPACE } from '../mathlive-instance';
|
|
5
|
+
import { loadMathLive, getMacros, MATH_MODE_SPACE, applyShadowStyles } from '../mathlive-instance';
|
|
6
6
|
import { toMathLive, fromMathLive, keyToAction, KEYSTROKES } from '../latex-bridge';
|
|
7
7
|
|
|
8
8
|
const log = debug('pie-lib:math-input-mathlive:input');
|
|
@@ -75,6 +75,10 @@ export class Input extends React.Component {
|
|
|
75
75
|
// defaults to an empty string.
|
|
76
76
|
this.mathField.mathModeSpace = MATH_MODE_SPACE;
|
|
77
77
|
|
|
78
|
+
// The mathfield renders in shadow DOM, so page CSS cannot reach it - the
|
|
79
|
+
// stretchy-accent fix has to be injected there directly.
|
|
80
|
+
applyShadowStyles(this.mathField);
|
|
81
|
+
|
|
78
82
|
this.mathField.addEventListener('input', this.onInputEdit);
|
|
79
83
|
|
|
80
84
|
// Belt and braces: drop anything a previous mount may have left behind.
|
package/src/mf/static.jsx
CHANGED
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { styled } from '@mui/material/styles';
|
|
4
4
|
import debug from 'debug';
|
|
5
|
-
import { loadMathLive, getMacros, latexToMarkup, MATH_MODE_SPACE } from '../mathlive-instance';
|
|
5
|
+
import { loadMathLive, getMacros, latexToMarkup, MATH_MODE_SPACE, applyShadowStyles } from '../mathlive-instance';
|
|
6
6
|
import { toMathLive, fromMathLive, fieldIds } from '../latex-bridge';
|
|
7
7
|
import { placeholderStyles } from './common-styles';
|
|
8
8
|
|
|
@@ -93,6 +93,10 @@ export default class Static extends React.Component {
|
|
|
93
93
|
this.mathField.mathModeSpace = MATH_MODE_SPACE;
|
|
94
94
|
this.mathField.value = toMathLive(this.props.latex);
|
|
95
95
|
|
|
96
|
+
// The mathfield renders in shadow DOM, so page CSS cannot reach it - the
|
|
97
|
+
// stretchy-accent fix has to be injected there directly.
|
|
98
|
+
applyShadowStyles(this.mathField);
|
|
99
|
+
|
|
96
100
|
this.mathField.addEventListener('input', this.onPromptInput);
|
|
97
101
|
this.mathField.addEventListener('focusin', this.onPromptFocus);
|
|
98
102
|
this.mathField.addEventListener('keydown', this.onKeyDown);
|