@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.
- package/README.md +84 -109
- package/dist/_virtual/_rolldown/runtime.js +11 -0
- package/dist/horizontal-keypad.d.ts +22 -0
- package/dist/horizontal-keypad.js +54 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +12 -0
- package/dist/keypad/index.d.ts +40 -0
- package/dist/keypad/index.js +245 -0
- package/dist/keypad/keys-layout.d.ts +7 -0
- package/dist/keypad/keys-layout.js +5 -0
- package/dist/keys/basic-operators.d.ts +5 -0
- package/dist/keys/basic-operators.js +30 -0
- package/dist/keys/chars.d.ts +5 -0
- package/dist/keys/comparison.d.ts +4 -0
- package/dist/keys/comparison.js +32 -0
- package/dist/keys/constants.d.ts +4 -0
- package/dist/keys/constants.js +35 -0
- package/dist/keys/digits.d.ts +15 -0
- package/dist/keys/digits.js +34 -0
- package/dist/keys/edit.d.ts +6 -0
- package/dist/keys/edit.js +9 -0
- package/dist/keys/exponent.d.ts +4 -0
- package/dist/keys/exponent.js +28 -0
- package/dist/keys/fractions.d.ts +3 -0
- package/dist/keys/fractions.js +27 -0
- package/dist/keys/geometry.d.ts +23 -0
- package/dist/keys/geometry.js +127 -0
- package/dist/keys/grades.d.ts +9 -0
- package/dist/keys/grades.js +414 -0
- package/dist/keys/index.d.ts +6 -0
- package/dist/keys/index.js +50 -0
- package/dist/keys/log.d.ts +3 -0
- package/dist/keys/log.js +25 -0
- package/dist/keys/logic.d.ts +2 -0
- package/dist/keys/logic.js +13 -0
- package/dist/keys/matrices.d.ts +2 -0
- package/dist/keys/matrices.js +17 -0
- package/dist/keys/misc.d.ts +10 -0
- package/dist/keys/misc.js +60 -0
- package/dist/keys/navigation.d.ts +2 -0
- package/dist/keys/navigation.js +13 -0
- package/dist/keys/operators.d.ts +1 -0
- package/dist/keys/operators.js +11 -0
- package/dist/keys/statistics.d.ts +5 -0
- package/dist/keys/statistics.js +38 -0
- package/dist/keys/sub-sup.d.ts +2 -0
- package/dist/keys/sub-sup.js +17 -0
- package/dist/keys/trigonometry.d.ts +6 -0
- package/dist/keys/trigonometry.js +43 -0
- package/dist/keys/utils.d.ts +5 -0
- package/dist/keys/utils.js +24 -0
- package/dist/keys/vars.d.ts +3 -0
- package/dist/keys/vars.js +22 -0
- package/dist/latex-bridge.d.ts +197 -0
- package/dist/latex-bridge.js +210 -0
- package/dist/longdiv.d.ts +24 -0
- package/dist/longdiv.js +43 -0
- package/dist/math-input.d.ts +26 -0
- package/dist/math-input.js +74 -0
- package/dist/mathlive-instance.d.ts +221 -0
- package/dist/mathlive-instance.js +159 -0
- package/dist/mf/common-styles.d.ts +594 -0
- package/dist/mf/common-styles.js +80 -0
- package/dist/mf/field-label.d.ts +16 -0
- package/dist/mf/field-label.js +13 -0
- package/dist/mf/index.d.ts +4 -0
- package/dist/mf/index.js +21 -0
- package/dist/mf/input.d.ts +94 -0
- package/dist/mf/input.js +127 -0
- package/dist/mf/static.d.ts +143 -0
- package/dist/mf/static.js +230 -0
- package/dist/selection.d.ts +43 -0
- package/dist/selection.js +39 -0
- package/package.json +32 -18
- package/lib/__tests__/composites.test.js +0 -255
- package/lib/__tests__/drop-in-compat.test.js +0 -133
- package/lib/__tests__/embedded-fonts.test.js +0 -121
- package/lib/__tests__/index.test.js +0 -118
- package/lib/__tests__/input-imperative.test.js +0 -155
- package/lib/__tests__/input-selection.test.js +0 -103
- package/lib/__tests__/keypad-label.test.js +0 -231
- package/lib/__tests__/keypad-render.test.js +0 -206
- package/lib/__tests__/latex-bridge.test.js +0 -386
- package/lib/__tests__/mathlive-instance.test.js +0 -419
- package/lib/__tests__/mount-race.test.js +0 -218
- package/lib/__tests__/selection.test.js +0 -195
- package/lib/__tests__/static-prompts.test.js +0 -405
- package/lib/__tests__/tmp-markup.test.js +0 -26
- package/lib/horizontal-keypad.js +0 -101
- package/lib/horizontal-keypad.js.map +0 -1
- package/lib/index.js +0 -143
- package/lib/index.js.map +0 -1
- package/lib/keypad/index.js +0 -431
- package/lib/keypad/index.js.map +0 -1
- package/lib/keypad/keys-layout.js +0 -22
- package/lib/keypad/keys-layout.js.map +0 -1
- package/lib/keys/basic-operators.js +0 -33
- package/lib/keys/basic-operators.js.map +0 -1
- package/lib/keys/chars.js +0 -12
- package/lib/keys/chars.js.map +0 -1
- package/lib/keys/comparison.js +0 -39
- package/lib/keys/comparison.js.map +0 -1
- package/lib/keys/constants.js +0 -37
- package/lib/keys/constants.js.map +0 -1
- package/lib/keys/digits.js +0 -46
- package/lib/keys/digits.js.map +0 -1
- package/lib/keys/edit.js +0 -14
- package/lib/keys/edit.js.map +0 -1
- package/lib/keys/exponent.js +0 -30
- package/lib/keys/exponent.js.map +0 -1
- package/lib/keys/fractions.js +0 -29
- package/lib/keys/fractions.js.map +0 -1
- package/lib/keys/geometry.js +0 -143
- package/lib/keys/geometry.js.map +0 -1
- package/lib/keys/grades.js +0 -259
- package/lib/keys/grades.js.map +0 -1
- package/lib/keys/index.js +0 -35
- package/lib/keys/index.js.map +0 -1
- package/lib/keys/log.js +0 -27
- package/lib/keys/log.js.map +0 -1
- package/lib/keys/logic.js +0 -19
- package/lib/keys/logic.js.map +0 -1
- package/lib/keys/matrices.js +0 -19
- package/lib/keys/matrices.js.map +0 -1
- package/lib/keys/misc.js +0 -69
- package/lib/keys/misc.js.map +0 -1
- package/lib/keys/navigation.js +0 -20
- package/lib/keys/navigation.js.map +0 -1
- package/lib/keys/operators.js +0 -15
- package/lib/keys/operators.js.map +0 -1
- package/lib/keys/statistics.js +0 -40
- package/lib/keys/statistics.js.map +0 -1
- package/lib/keys/sub-sup.js +0 -19
- package/lib/keys/sub-sup.js.map +0 -1
- package/lib/keys/trigonometry.js +0 -45
- package/lib/keys/trigonometry.js.map +0 -1
- package/lib/keys/utils.js +0 -87
- package/lib/keys/utils.js.map +0 -1
- package/lib/keys/vars.js +0 -24
- package/lib/keys/vars.js.map +0 -1
- package/lib/latex-bridge.js +0 -445
- package/lib/latex-bridge.js.map +0 -1
- package/lib/math-input.js +0 -143
- package/lib/math-input.js.map +0 -1
- package/lib/mathlive-instance.js +0 -585
- package/lib/mathlive-instance.js.map +0 -1
- package/lib/mf/common-styles.js +0 -203
- package/lib/mf/common-styles.js.map +0 -1
- package/lib/mf/index.js +0 -84
- package/lib/mf/index.js.map +0 -1
- package/lib/mf/input.js +0 -372
- package/lib/mf/input.js.map +0 -1
- package/lib/mf/static.js +0 -500
- package/lib/mf/static.js.map +0 -1
- package/lib/selection.js +0 -148
- package/lib/selection.js.map +0 -1
- package/src/__tests__/composites.test.jsx +0 -220
- package/src/__tests__/drop-in-compat.test.jsx +0 -112
- package/src/__tests__/index.test.jsx +0 -109
- package/src/__tests__/input-imperative.test.jsx +0 -144
- package/src/__tests__/input-selection.test.jsx +0 -64
- package/src/__tests__/keypad-label.test.jsx +0 -283
- package/src/__tests__/keypad-render.test.jsx +0 -123
- package/src/__tests__/latex-bridge.test.js +0 -359
- package/src/__tests__/mathlive-instance.test.js +0 -418
- package/src/__tests__/mount-race.test.jsx +0 -142
- package/src/__tests__/selection.test.js +0 -212
- package/src/__tests__/static-prompts.test.jsx +0 -364
- package/src/horizontal-keypad.jsx +0 -73
- package/src/index.jsx +0 -63
- package/src/keypad/index.jsx +0 -386
- package/src/keypad/keys-layout.js +0 -16
- package/src/keys/basic-operators.js +0 -32
- package/src/keys/chars.js +0 -5
- package/src/keys/comparison.js +0 -28
- package/src/keys/constants.js +0 -35
- package/src/keys/digits.js +0 -40
- package/src/keys/edit.js +0 -3
- package/src/keys/exponent.js +0 -28
- package/src/keys/fractions.js +0 -26
- package/src/keys/geometry.js +0 -149
- package/src/keys/grades.js +0 -367
- package/src/keys/index.js +0 -20
- package/src/keys/log.js +0 -22
- package/src/keys/logic.js +0 -15
- package/src/keys/matrices.js +0 -15
- package/src/keys/misc.js +0 -71
- package/src/keys/navigation.js +0 -8
- package/src/keys/operators.js +0 -10
- package/src/keys/statistics.js +0 -38
- package/src/keys/sub-sup.js +0 -15
- package/src/keys/trigonometry.js +0 -15
- package/src/keys/utils.js +0 -66
- package/src/keys/vars.js +0 -19
- package/src/latex-bridge.js +0 -449
- package/src/math-input.jsx +0 -115
- package/src/mathlive-instance.js +0 -582
- package/src/mf/common-styles.js +0 -210
- package/src/mf/index.js +0 -30
- package/src/mf/input.jsx +0 -328
- package/src/mf/static.jsx +0 -479
- package/src/selection.js +0 -146
package/src/math-input.jsx
DELETED
|
@@ -1,115 +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
|
-
|
|
6
|
-
import * as mf from './mf';
|
|
7
|
-
import { baseSet } from './keys';
|
|
8
|
-
import KeyPad from './keypad';
|
|
9
|
-
import { keyToAction } from './latex-bridge';
|
|
10
|
-
|
|
11
|
-
const log = debug('pie-lib:math-input-mathlive');
|
|
12
|
-
|
|
13
|
-
const grey = 'rgba(0, 0, 0, 0.23)';
|
|
14
|
-
|
|
15
|
-
const MathInputContainer = styled('div')(({ theme, focused }) => ({
|
|
16
|
-
borderRadius: '4px',
|
|
17
|
-
border: `solid 1px ${focused ? theme.palette.primary.main : grey}`,
|
|
18
|
-
marginTop: theme.spacing(1),
|
|
19
|
-
marginBottom: theme.spacing(1),
|
|
20
|
-
transition: 'border 200ms linear',
|
|
21
|
-
}));
|
|
22
|
-
|
|
23
|
-
const PadContainer = styled('div')({
|
|
24
|
-
width: '100%',
|
|
25
|
-
display: 'flex',
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const StyledInput = styled(mf.Input)(({ theme }) => ({
|
|
29
|
-
width: '100%',
|
|
30
|
-
transition: 'border 200ms linear',
|
|
31
|
-
padding: theme.spacing(1),
|
|
32
|
-
'& math-field:focus-within': {
|
|
33
|
-
outline: 'none',
|
|
34
|
-
boxShadow: 'none',
|
|
35
|
-
},
|
|
36
|
-
}));
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* MathLive-backed equivalent of @pie-lib/math-input's `MathInput`.
|
|
40
|
-
* Same props (`keyset`, `latex`, `onChange`, `displayMode`).
|
|
41
|
-
*/
|
|
42
|
-
export class MathInput extends React.Component {
|
|
43
|
-
static propTypes = {
|
|
44
|
-
className: PropTypes.string,
|
|
45
|
-
keyset: PropTypes.array,
|
|
46
|
-
displayMode: PropTypes.oneOf(['block', 'block-on-focus']),
|
|
47
|
-
latex: PropTypes.string,
|
|
48
|
-
onChange: PropTypes.func,
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
static defaultProps = {
|
|
52
|
-
keyset: [],
|
|
53
|
-
displayMode: 'block',
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
constructor(props) {
|
|
57
|
-
super(props);
|
|
58
|
-
this.state = { focused: false };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
keypadPress = (key) => {
|
|
62
|
-
log('[keypadPress] key:', key);
|
|
63
|
-
|
|
64
|
-
if (!this.input) {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const action = keyToAction(key);
|
|
69
|
-
|
|
70
|
-
if (!action) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (action.type === 'command') {
|
|
75
|
-
this.input.keystroke(action.value);
|
|
76
|
-
} else {
|
|
77
|
-
this.input.write(action.value);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
inputFocus = () => this.setState({ focused: true });
|
|
82
|
-
|
|
83
|
-
inputBlur = () => this.setState({ focused: false });
|
|
84
|
-
|
|
85
|
-
changeLatex = (l) => {
|
|
86
|
-
const { onChange } = this.props;
|
|
87
|
-
|
|
88
|
-
if (onChange && l !== this.props.latex) {
|
|
89
|
-
log('[changeLatex]', l, this.props.latex);
|
|
90
|
-
onChange(l);
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
render() {
|
|
95
|
-
const { className, keyset, latex } = this.props;
|
|
96
|
-
const { focused } = this.state;
|
|
97
|
-
|
|
98
|
-
return (
|
|
99
|
-
<MathInputContainer className={className} focused={focused}>
|
|
100
|
-
<StyledInput
|
|
101
|
-
innerRef={(r) => (this.input = r)}
|
|
102
|
-
onFocus={this.inputFocus}
|
|
103
|
-
onBlur={this.inputBlur}
|
|
104
|
-
latex={latex}
|
|
105
|
-
onChange={this.changeLatex}
|
|
106
|
-
/>
|
|
107
|
-
<PadContainer>
|
|
108
|
-
<KeyPad baseSet={baseSet} additionalKeys={keyset} onPress={this.keypadPress} />
|
|
109
|
-
</PadContainer>
|
|
110
|
-
</MathInputContainer>
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export default MathInput;
|
package/src/mathlive-instance.js
DELETED
|
@@ -1,582 +0,0 @@
|
|
|
1
|
-
import debug from 'debug';
|
|
2
|
-
import { NEWLINE_EMBED, toMathLive, withVisibleEmptySlots } from './latex-bridge';
|
|
3
|
-
import { ROOT_INDEX_CSS, PARALLEL_CSS } from './mf/common-styles';
|
|
4
|
-
|
|
5
|
-
const log = debug('pie-lib:math-input-mathlive:instance');
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* MathLive bootstrap + compatibility shims for @pie-lib/math-input's public API.
|
|
9
|
-
*
|
|
10
|
-
* MathLive is loaded lazily and only in the browser: the editor bundle is
|
|
11
|
-
* ~227KB gzip and registers custom elements, so it must never run during SSR.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
let mathlive;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Where the KaTeX woff2 fonts are served from, as configured by the host app.
|
|
18
|
-
* `undefined` means "not configured"; `null` means "explicitly disabled".
|
|
19
|
-
*/
|
|
20
|
-
let fontsDirectory;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Turn a fonts path into a fully-qualified URL.
|
|
24
|
-
*
|
|
25
|
-
* MathLive's default `fontsDirectory` is the relative `'./fonts/'`, which it
|
|
26
|
-
* resolves against its own script URL. Under a bundler (webpack/Next) that
|
|
27
|
-
* script URL is empty, so `new URL('./fonts/', '')` throws
|
|
28
|
-
* "Failed to construct 'URL': Invalid base URL" from `loadFonts`.
|
|
29
|
-
* Resolving against `document.baseURI` avoids that entirely and honours any
|
|
30
|
-
* <base href> / asset prefix the host app uses.
|
|
31
|
-
*/
|
|
32
|
-
const absoluteFontsDirectory = (dir) => {
|
|
33
|
-
if (!dir) {
|
|
34
|
-
return dir;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Already absolute (http://, https://, //cdn...) - MathLive handles these.
|
|
38
|
-
if (/^(?:[a-z+]+:)?\/\//i.test(dir)) {
|
|
39
|
-
return dir;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
return new URL(dir, document.baseURI).href;
|
|
44
|
-
} catch (e) {
|
|
45
|
-
log('could not resolve fontsDirectory "%s": %s', dir, e && e.message);
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Version-pinned CDN fallback for the KaTeX fonts.
|
|
52
|
-
*
|
|
53
|
-
* Some symbols are composed from Private Use Area glyphs that exist only in
|
|
54
|
-
* those fonts: `\\neq` is U+E020 (a negation slash) overlapped on `=`, and
|
|
55
|
-
* `\\nsim` / `\\ncong` work the same way. With no fonts the browser substitutes
|
|
56
|
-
* an arbitrary glyph for U+E020, so those keys render as garbage rather than
|
|
57
|
-
* simply falling back to a plainer face.
|
|
58
|
-
*
|
|
59
|
-
* The version comes from MathLive itself, so the fonts always match the engine.
|
|
60
|
-
* Hosts that would rather not depend on a third party should serve the fonts and
|
|
61
|
-
* call `configureFonts('/their/path')`, or pass `null` to opt out.
|
|
62
|
-
*/
|
|
63
|
-
const FALLBACK_MATHLIVE_VERSION = '0.110.0';
|
|
64
|
-
|
|
65
|
-
const cdnFontsDirectory = () => {
|
|
66
|
-
const version = (mathlive && mathlive.version && mathlive.version.mathlive) || FALLBACK_MATHLIVE_VERSION;
|
|
67
|
-
|
|
68
|
-
// NOTE: `/fonts`, not `/dist/fonts` - the published package root holds them.
|
|
69
|
-
return `https://unpkg.com/mathlive@${version}/fonts`;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Tell the package where the MathLive fonts are served from.
|
|
74
|
-
*
|
|
75
|
-
* Call before `loadMathLive()`.
|
|
76
|
-
*
|
|
77
|
-
* - a path/URL: served by you, no third-party request
|
|
78
|
-
* - `null`: skip font loading; note `\\neq`, `\\nsim` and `\\ncong` then
|
|
79
|
-
* render incorrectly, being built from Private Use Area glyphs
|
|
80
|
-
* - not called: falls back to a version-pinned CDN
|
|
81
|
-
*
|
|
82
|
-
* The fonts ship inside the `mathlive` package (`node_modules/mathlive/fonts`)
|
|
83
|
-
* ready to copy into whatever the host app serves statically.
|
|
84
|
-
*
|
|
85
|
-
* @param {string|null} [dir]
|
|
86
|
-
*/
|
|
87
|
-
export const configureFonts = (dir) => {
|
|
88
|
-
fontsDirectory = dir;
|
|
89
|
-
|
|
90
|
-
if (mathlive) {
|
|
91
|
-
mathlive.MathfieldElement.fontsDirectory = dir === undefined ? cdnFontsDirectory() : absoluteFontsDirectory(dir);
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* What the spacebar inserts in math mode.
|
|
97
|
-
*
|
|
98
|
-
* MathLive's `mathModeSpace` defaults to an empty string, which makes the
|
|
99
|
-
* spacebar a no-op - users cannot type a space at all. `\\ ` (a LaTeX control
|
|
100
|
-
* space) is what MathQuill serialised spaces as, so existing content and the
|
|
101
|
-
* MathJax renderer in @pie-lib/math-rendering both already understand it.
|
|
102
|
-
*
|
|
103
|
-
* A literal " " is not usable: LaTeX skips raw whitespace.
|
|
104
|
-
*/
|
|
105
|
-
export const MATH_MODE_SPACE = '\\ ';
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Custom macros for the pie-specific commands that the MathQuill fork
|
|
109
|
-
* understood but stock LaTeX does not.
|
|
110
|
-
*
|
|
111
|
-
* Kept deliberately in sync with the MathJax macros in
|
|
112
|
-
* @pie-lib/math-rendering (`render-math.js`) so the same latex renders the same
|
|
113
|
-
* way in the editor and in the static renderer.
|
|
114
|
-
*/
|
|
115
|
-
export const PIE_MACROS = {
|
|
116
|
-
// Argument-less symbols. Atomic is correct here: backspace should remove the
|
|
117
|
-
// whole glyph, and there is nothing to type into.
|
|
118
|
-
parallelogram: '\\htmlData{pie-macro=parallelogram}{\\unicode{"25B1}}',
|
|
119
|
-
// Same glyph as the built-in `\\parallel` (U+2225), tagged so it can be sized -
|
|
120
|
-
// see `parallelStyles`.
|
|
121
|
-
parallel: '\\htmlData{pie-macro=parallel}{\\unicode{"2225}}',
|
|
122
|
-
napprox: '\\not\\approx',
|
|
123
|
-
nsim: '\\not\\sim',
|
|
124
|
-
ncong: '\\not\\cong',
|
|
125
|
-
nparallel: '\\not\\parallel',
|
|
126
|
-
perpendicular: '\\perp',
|
|
127
|
-
divide: '\\div',
|
|
128
|
-
degree: '^{\\circ}',
|
|
129
|
-
square: '\\unicode{"25A1}',
|
|
130
|
-
|
|
131
|
-
// Argument-taking commands, kept ONLY so previously authored latex still
|
|
132
|
-
// parses and renders. They are never used for editing: MathLive serialises a
|
|
133
|
-
// macro from the arguments it was created with, so text typed inside the
|
|
134
|
-
// expansion never comes back out of `getValue('latex')`. Anything entering a
|
|
135
|
-
// mathfield is rewritten to the native equivalent first - see
|
|
136
|
-
// `toNativeCommands` in latex-bridge.
|
|
137
|
-
longdiv: '\\enclose{longdiv}{#1}',
|
|
138
|
-
overarc: '\\overparen{#1}',
|
|
139
|
-
abs: '\\left|#1\\right|',
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Registry for `registerEmbed`.
|
|
144
|
-
*
|
|
145
|
-
* MathLive has no embed plugin system, so an embed is expressed as a macro that
|
|
146
|
-
* expands to real LaTeX. `\htmlData{embed=<name>}` tags the rendered output so
|
|
147
|
-
* consumers can target it with CSS exactly as they targeted the old
|
|
148
|
-
* `htmlString` markup.
|
|
149
|
-
*
|
|
150
|
-
* Known embeds map to native constructs:
|
|
151
|
-
* - `newLine` -> structural, handled by latex-bridge (`\displaylines` + `\\`)
|
|
152
|
-
* - `answerBlock`-> `\placeholder{}` (interactive, read via the prompts API)
|
|
153
|
-
*
|
|
154
|
-
* @type {Map<string, {factory: Function, latex: string}>}
|
|
155
|
-
*/
|
|
156
|
-
const embeds = new Map();
|
|
157
|
-
|
|
158
|
-
const KNOWN_EMBED_LATEX = {
|
|
159
|
-
// newLine is structural: it is translated by latex-bridge before it ever
|
|
160
|
-
// reaches MathLive, so the macro only needs to be harmless.
|
|
161
|
-
newLine: '',
|
|
162
|
-
answerBlock: '\\placeholder{}',
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
let loadWarned = false;
|
|
166
|
-
|
|
167
|
-
/** Apply post-load configuration exactly once. */
|
|
168
|
-
const configureLoaded = () => {
|
|
169
|
-
// Always replace MathLive's relative `'./fonts/'` default: it throws
|
|
170
|
-
// "Invalid base URL" when bundled. Use the host's copy if one was configured,
|
|
171
|
-
// otherwise the version-pinned CDN.
|
|
172
|
-
if (fontsDirectory === undefined) {
|
|
173
|
-
log(
|
|
174
|
-
'no fontsDirectory configured - falling back to the CDN (%s). ' +
|
|
175
|
-
'Serve the fonts yourself and call configureFonts("/path/to/fonts") to avoid the network request, ' +
|
|
176
|
-
'or configureFonts(null) to opt out.',
|
|
177
|
-
cdnFontsDirectory(),
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
mathlive.MathfieldElement.fontsDirectory =
|
|
182
|
-
fontsDirectory === undefined ? cdnFontsDirectory() : absoluteFontsDirectory(fontsDirectory);
|
|
183
|
-
|
|
184
|
-
injectCoreStylesheet();
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* CSS that has to live *inside* a mathfield's shadow root.
|
|
189
|
-
*
|
|
190
|
-
* A live `<math-field>` renders in shadow DOM (`attachShadow({mode:'open'})`),
|
|
191
|
-
* so nothing in the host page's stylesheets reaches it. The stretchy-accent
|
|
192
|
-
* problem therefore needs fixing twice: once for static markup (see
|
|
193
|
-
* `placeholderStyles`) and once here for the editor.
|
|
194
|
-
*
|
|
195
|
-
* MathLive centres an accent with `margin-left: <half the content width>` as an
|
|
196
|
-
* inline style. For a stretchy accent that also shrinks its containing block
|
|
197
|
-
* (`.ML__center` is position:relative), so the arc ends up half-width and offset
|
|
198
|
-
* - visibly failing to span the text underneath.
|
|
199
|
-
*
|
|
200
|
-
* `:has(.ML__stretchy)` keeps the fix to stretchy accents; single-glyph accents
|
|
201
|
-
* (`\hat{x}`, `\vec{x}`) still need their centring offset.
|
|
202
|
-
*/
|
|
203
|
-
/**
|
|
204
|
-
* Marker put on the host `<math-field>` while a `\placeholder{}` is selected,
|
|
205
|
-
* so the caret rules below apply to a placeholder and nothing else.
|
|
206
|
-
*/
|
|
207
|
-
export const PLACEHOLDER_CARET_CLASS = 'pie-caret-in-placeholder';
|
|
208
|
-
|
|
209
|
-
/** What MathLive serialises a `#?` slot as. */
|
|
210
|
-
const PLACEHOLDER_LATEX = '\\placeholder{}';
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* A blinking caret inside a selected placeholder box.
|
|
214
|
-
*
|
|
215
|
-
* A keypad key such as `\frac` inserts `\frac{#?}{#?}`. MathLive turns each
|
|
216
|
-
* `#?` into a `\placeholder{}` atom and *selects* the first one. That atom is a
|
|
217
|
-
* leaf - the offsets around it are "before" and "after", there is no position
|
|
218
|
-
* inside it - so MathLive renders **no caret element at all** and shows only
|
|
219
|
-
* the selection highlight. The box therefore looked focused (blue) but dead,
|
|
220
|
-
* where MathQuill used to blink a cursor inside its empty box.
|
|
221
|
-
*
|
|
222
|
-
* So the caret is drawn here. `:not(:has(.ML__selected))` picks the innermost
|
|
223
|
-
* selected element (MathLive puts the class on a wrapper *and* the glyph span,
|
|
224
|
-
* which would otherwise give two carets), and the `ML__caret-blink` keyframes
|
|
225
|
-
* plus `--_caret-color` are MathLive's own, already in this shadow root.
|
|
226
|
-
*
|
|
227
|
-
* Typing still replaces the whole placeholder, which for an empty box is
|
|
228
|
-
* indistinguishable from inserting at the caret.
|
|
229
|
-
*/
|
|
230
|
-
const PLACEHOLDER_CARET_CSS = [
|
|
231
|
-
`:host(.${PLACEHOLDER_CARET_CLASS}) .ML__selected:not(:has(.ML__selected)){position:relative}`,
|
|
232
|
-
`:host(.${PLACEHOLDER_CARET_CLASS}) .ML__selected:not(:has(.ML__selected))::after{`,
|
|
233
|
-
'content:"";position:absolute;left:50%;top:12%;height:76%;',
|
|
234
|
-
'border-right:2px solid var(--_caret-color, currentColor);',
|
|
235
|
-
'animation:ML__caret-blink 1.05s step-end forwards infinite}',
|
|
236
|
-
].join('');
|
|
237
|
-
|
|
238
|
-
// A live mathfield needs the readable radical index too: `rootIndexStyles` is
|
|
239
|
-
// written for static markup in the host page, which cannot cross the shadow
|
|
240
|
-
// boundary.
|
|
241
|
-
const SHADOW_CSS =
|
|
242
|
-
'.ML__latex .ML__center:has(.ML__stretchy){margin-left:0 !important}' +
|
|
243
|
-
ROOT_INDEX_CSS +
|
|
244
|
-
PARALLEL_CSS +
|
|
245
|
-
PLACEHOLDER_CARET_CSS;
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Keep {@link PLACEHOLDER_CARET_CLASS} in step with the selection.
|
|
249
|
-
*
|
|
250
|
-
* Gating on the class is what keeps the caret specific to placeholders: an
|
|
251
|
-
* ordinary selection (`x+1`) must not sprout one. MathLive fires
|
|
252
|
-
* `selection-change` for every caret move, including the one that lands on the
|
|
253
|
-
* placeholder straight after an insert.
|
|
254
|
-
*
|
|
255
|
-
* @param {HTMLElement} mathField
|
|
256
|
-
* @returns {function|undefined} teardown, to call on unmount
|
|
257
|
-
*/
|
|
258
|
-
export const trackPlaceholderCaret = (mathField) => {
|
|
259
|
-
if (!mathField || typeof mathField.addEventListener !== 'function') {
|
|
260
|
-
return undefined;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const update = () => {
|
|
264
|
-
let selected = '';
|
|
265
|
-
|
|
266
|
-
try {
|
|
267
|
-
// A collapsed selection already has a real MathLive caret.
|
|
268
|
-
if (!mathField.selectionIsCollapsed && mathField.selection) {
|
|
269
|
-
selected = mathField.getValue(mathField.selection, 'latex');
|
|
270
|
-
}
|
|
271
|
-
} catch (e) {
|
|
272
|
-
log('could not read the selection: %s', e && e.message);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (mathField.classList) {
|
|
276
|
-
mathField.classList.toggle(PLACEHOLDER_CARET_CLASS, selected === PLACEHOLDER_LATEX);
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
mathField.addEventListener('selection-change', update);
|
|
281
|
-
update();
|
|
282
|
-
|
|
283
|
-
return () => mathField.removeEventListener('selection-change', update);
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Apply {@link SHADOW_CSS} inside a mathfield's shadow root.
|
|
288
|
-
*
|
|
289
|
-
* Uses `adoptedStyleSheets` when available (which is how MathLive loads its own
|
|
290
|
-
* stylesheets) and falls back to appending a <style> node.
|
|
291
|
-
*
|
|
292
|
-
* @param {HTMLElement} mathField
|
|
293
|
-
*/
|
|
294
|
-
export const applyShadowStyles = (mathField) => {
|
|
295
|
-
const root = mathField && mathField.shadowRoot;
|
|
296
|
-
|
|
297
|
-
if (!root) {
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
try {
|
|
302
|
-
if ('adoptedStyleSheets' in root && typeof CSSStyleSheet !== 'undefined') {
|
|
303
|
-
// Do not re-add on a re-render.
|
|
304
|
-
if (root.__pieAccentSheet) {
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const sheet = new CSSStyleSheet();
|
|
309
|
-
|
|
310
|
-
sheet.replaceSync(SHADOW_CSS);
|
|
311
|
-
root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
|
|
312
|
-
root.__pieAccentSheet = sheet;
|
|
313
|
-
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (!root.querySelector('style[data-pie-accent]')) {
|
|
318
|
-
const style = document.createElement('style');
|
|
319
|
-
|
|
320
|
-
style.setAttribute('data-pie-accent', '');
|
|
321
|
-
style.textContent = SHADOW_CSS;
|
|
322
|
-
root.appendChild(style);
|
|
323
|
-
}
|
|
324
|
-
} catch (e) {
|
|
325
|
-
log('could not apply shadow styles to the mathfield: %s', e && e.message);
|
|
326
|
-
}
|
|
327
|
-
};
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Ensure MathLive's "core" stylesheet is in the document.
|
|
331
|
-
*
|
|
332
|
-
* `convertLatexToMarkup` - which renders keypad labels and static math - does
|
|
333
|
-
* NOT inject it, while `renderMathInElement` and live mathfields do. Without it
|
|
334
|
-
* the generated markup is unstyled: glyphs misalign and the <svg> used by
|
|
335
|
-
* stretchy accents lays out statically at an unbounded width.
|
|
336
|
-
*
|
|
337
|
-
* Hosts that can import CSS should `import 'mathlive/static.css'`. For bundles
|
|
338
|
-
* that cannot handle CSS imports (pie-elements ships JS-only web components),
|
|
339
|
-
* this triggers MathLive's own injection against a detached element, so the
|
|
340
|
-
* styles come from MathLive itself rather than being duplicated here.
|
|
341
|
-
*/
|
|
342
|
-
const injectCoreStylesheet = () => {
|
|
343
|
-
if (typeof document === 'undefined' || !mathlive || !mathlive.renderMathInElement) {
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
try {
|
|
348
|
-
mathlive.renderMathInElement(document.createElement('span'));
|
|
349
|
-
} catch (e) {
|
|
350
|
-
log('could not trigger MathLive core stylesheet injection: %s', e && e.message);
|
|
351
|
-
}
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Load MathLive synchronously when the bundler allows it.
|
|
356
|
-
*
|
|
357
|
-
* `require` keeps MathLive inside the host's bundle. An `import()` would make
|
|
358
|
-
* webpack emit a separate chunk, and a deployment that does not serve that
|
|
359
|
-
* chunk fails with a 404 at runtime - leaving every mathfield and keypad label
|
|
360
|
-
* blank with no obvious cause. Being in the main bundle also makes the engine
|
|
361
|
-
* available on first render, so callers that run synchronously (applyStaticMath,
|
|
362
|
-
* keypad labels) work without a second pass.
|
|
363
|
-
*
|
|
364
|
-
* Returns undefined during SSR or if the module is genuinely unavailable.
|
|
365
|
-
*
|
|
366
|
-
* @returns {object|undefined}
|
|
367
|
-
*/
|
|
368
|
-
export const loadMathLiveSync = () => {
|
|
369
|
-
if (typeof window === 'undefined') {
|
|
370
|
-
return undefined;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
if (!mathlive) {
|
|
374
|
-
try {
|
|
375
|
-
// eslint-disable-next-line global-require
|
|
376
|
-
mathlive = require('mathlive');
|
|
377
|
-
configureLoaded();
|
|
378
|
-
} catch (e) {
|
|
379
|
-
log('synchronous MathLive load unavailable: %s', e && e.message);
|
|
380
|
-
return undefined;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
return mathlive;
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Load MathLive. Prefers the synchronous path; falls back to a dynamic import.
|
|
389
|
-
* Resolves to undefined during SSR.
|
|
390
|
-
*
|
|
391
|
-
* @returns {Promise<object|undefined>}
|
|
392
|
-
*/
|
|
393
|
-
export const loadMathLive = async () => {
|
|
394
|
-
if (typeof window === 'undefined') {
|
|
395
|
-
return undefined;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
if (mathlive) {
|
|
399
|
-
return mathlive;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
if (loadMathLiveSync()) {
|
|
403
|
-
return mathlive;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
try {
|
|
407
|
-
mathlive = await import('mathlive');
|
|
408
|
-
configureLoaded();
|
|
409
|
-
} catch (e) {
|
|
410
|
-
// Surface this once: a silent failure here renders every field and keypad
|
|
411
|
-
// label blank, which is very hard to trace back to a module load.
|
|
412
|
-
// Skipped under test, where jsdom cannot load the browser build.
|
|
413
|
-
if (!loadWarned && process.env.NODE_ENV !== 'test') {
|
|
414
|
-
loadWarned = true;
|
|
415
|
-
// eslint-disable-next-line no-console
|
|
416
|
-
console.warn(
|
|
417
|
-
'[@pie-lib/math-input-mathlive] MathLive could not be loaded; math input will not render. ' +
|
|
418
|
-
'If this is a bundling issue, ensure "mathlive" is resolvable from the host bundle. Cause: ' +
|
|
419
|
-
(e && e.message),
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
return undefined;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
return mathlive;
|
|
427
|
-
};
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* Synchronous accessor for an already-loaded MathLive.
|
|
431
|
-
* @returns {object|undefined}
|
|
432
|
-
*/
|
|
433
|
-
export const getMathLive = () => mathlive;
|
|
434
|
-
|
|
435
|
-
/**
|
|
436
|
-
* The macro dictionary to hand to a mathfield: pie macros plus anything
|
|
437
|
-
* registered through `registerEmbed`.
|
|
438
|
-
*
|
|
439
|
-
* @returns {object}
|
|
440
|
-
*/
|
|
441
|
-
export const getMacros = () => {
|
|
442
|
-
const out = { ...PIE_MACROS };
|
|
443
|
-
|
|
444
|
-
embeds.forEach(({ latex }, name) => {
|
|
445
|
-
out[name] = `\\htmlData{embed=${name}}{${latex}}`;
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
return out;
|
|
449
|
-
};
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* Register an embed. API-compatible with @pie-lib/math-input's `registerEmbed`
|
|
453
|
-
* so existing callers do not change.
|
|
454
|
-
*
|
|
455
|
-
* MathQuill's factory returns `{ htmlString, text, latex }`. Only the
|
|
456
|
-
* declarative parts survive: `latex` round-trips through a macro and the
|
|
457
|
-
* styling hook becomes a `data-embed` attribute. Arbitrary DOM injection via
|
|
458
|
-
* `htmlString` has no MathLive equivalent and is ignored.
|
|
459
|
-
*
|
|
460
|
-
* @param {string} name
|
|
461
|
-
* @param {Function} factory
|
|
462
|
-
*/
|
|
463
|
-
export function registerEmbed(name, factory) {
|
|
464
|
-
if (!name || typeof factory !== 'function') {
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
let definition = {};
|
|
469
|
-
|
|
470
|
-
try {
|
|
471
|
-
definition = factory() || {};
|
|
472
|
-
} catch (e) {
|
|
473
|
-
log('embed factory threw for "%s": %s', name, e.message);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const latex = Object.prototype.hasOwnProperty.call(KNOWN_EMBED_LATEX, name)
|
|
477
|
-
? KNOWN_EMBED_LATEX[name]
|
|
478
|
-
: definition.mathliveLatex || '\\placeholder{}';
|
|
479
|
-
|
|
480
|
-
if (!Object.prototype.hasOwnProperty.call(KNOWN_EMBED_LATEX, name) && !definition.mathliveLatex) {
|
|
481
|
-
// Unknown third-party embed: degrade instead of throwing. `htmlString`
|
|
482
|
-
// cannot be reproduced, so it renders as an editable slot.
|
|
483
|
-
log(
|
|
484
|
-
'embed "%s" is not natively supported by MathLive; rendering as a placeholder. ' +
|
|
485
|
-
'Provide `mathliveLatex` in the factory result to control this.',
|
|
486
|
-
name,
|
|
487
|
-
);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
embeds.set(name, { factory, latex });
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
* Embeds registered so far. Exposed for tests/diagnostics.
|
|
495
|
-
* @returns {string[]}
|
|
496
|
-
*/
|
|
497
|
-
export const registeredEmbeds = () => Array.from(embeds.keys());
|
|
498
|
-
|
|
499
|
-
/** Reset the embed registry. Test helper. */
|
|
500
|
-
export const __resetEmbeds = () => embeds.clear();
|
|
501
|
-
|
|
502
|
-
/** Inject a stub MathLive instance. Test helper. */
|
|
503
|
-
export const __setMathLiveForTest = (stub) => {
|
|
504
|
-
mathlive = stub;
|
|
505
|
-
};
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* Render static math into an element. API-compatible with
|
|
509
|
-
* @pie-lib/math-input's `applyStaticMath`.
|
|
510
|
-
*
|
|
511
|
-
* Callers written against MathQuill invoke this synchronously from lifecycle
|
|
512
|
-
* methods, where MathLive (a dynamic import) may not have loaded yet. Rather
|
|
513
|
-
* than silently rendering nothing, this kicks off the load and re-applies once
|
|
514
|
-
* the engine is ready, so the element fills in on the next tick.
|
|
515
|
-
*
|
|
516
|
-
* @param {HTMLElement} element
|
|
517
|
-
* @param {string} [latex] assigned to the element before rendering
|
|
518
|
-
* @param {object} [options] `{ macros }` overrides
|
|
519
|
-
* @returns {HTMLElement|undefined} the element, or undefined if deferred
|
|
520
|
-
*/
|
|
521
|
-
export function applyStaticMath(element, latex, options = {}) {
|
|
522
|
-
if (!element || typeof window === 'undefined') {
|
|
523
|
-
return undefined;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// Capture the source now: if we defer, the element's textContent may have
|
|
527
|
-
// been overwritten by the time MathLive is ready.
|
|
528
|
-
const source = latex === undefined || latex === null ? element.textContent : latex;
|
|
529
|
-
const ml = getMathLive() || loadMathLiveSync();
|
|
530
|
-
|
|
531
|
-
if (!ml) {
|
|
532
|
-
log('applyStaticMath called before MathLive loaded - deferring until it is ready');
|
|
533
|
-
|
|
534
|
-
loadMathLive().then((loaded) => {
|
|
535
|
-
// The element may have been unmounted while loading.
|
|
536
|
-
if (loaded && element.isConnected !== false) {
|
|
537
|
-
applyStaticMath(element, source, options);
|
|
538
|
-
}
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
return undefined;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
element.innerHTML = ml.convertLatexToMarkup(withVisibleEmptySlots(toMathLive(source)), {
|
|
545
|
-
macros: { ...getMacros(), ...(options.macros || {}) },
|
|
546
|
-
});
|
|
547
|
-
|
|
548
|
-
return element;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
/**
|
|
552
|
-
* Convert latex to markup using the pie macro set. Used for keypad labels and
|
|
553
|
-
* any read-only rendering that does not need a live mathfield.
|
|
554
|
-
*
|
|
555
|
-
* @param {string} latex
|
|
556
|
-
* @param {object} [options]
|
|
557
|
-
* @returns {string} HTML markup, or '' if MathLive is not loaded
|
|
558
|
-
*/
|
|
559
|
-
export function latexToMarkup(latex, options = {}) {
|
|
560
|
-
// Try the synchronous load so the very first render can produce markup
|
|
561
|
-
// instead of falling back to raw latex.
|
|
562
|
-
const ml = getMathLive() || loadMathLiveSync();
|
|
563
|
-
|
|
564
|
-
if (!ml || !latex) {
|
|
565
|
-
return '';
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
try {
|
|
569
|
-
// Empty groups render as nothing in static markup, so give them a visible
|
|
570
|
-
// box. This covers every static path: keypad labels, mf.Static in display
|
|
571
|
-
// mode, and applyStaticMath.
|
|
572
|
-
return ml.convertLatexToMarkup(withVisibleEmptySlots(toMathLive(latex)), {
|
|
573
|
-
macros: { ...getMacros(), ...(options.macros || {}) },
|
|
574
|
-
...options,
|
|
575
|
-
});
|
|
576
|
-
} catch (e) {
|
|
577
|
-
log('convertLatexToMarkup failed for "%s": %s', latex, e.message);
|
|
578
|
-
return '';
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
export { NEWLINE_EMBED };
|