@pie-lib/math-input-mathlive 1.0.5 → 1.0.7
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/lib/__tests__/keypad-label.test.js +44 -0
- package/lib/__tests__/keypad-label.test.js.map +1 -0
- package/lib/__tests__/latex-bridge.test.js +65 -0
- package/lib/__tests__/latex-bridge.test.js.map +1 -1
- package/lib/__tests__/mathlive-instance.test.js +18 -0
- package/lib/__tests__/mathlive-instance.test.js.map +1 -1
- package/lib/keypad/index.js +6 -0
- package/lib/keypad/index.js.map +1 -1
- package/lib/latex-bridge.js +124 -4
- package/lib/latex-bridge.js.map +1 -1
- package/lib/mathlive-instance.js +15 -4
- package/lib/mathlive-instance.js.map +1 -1
- package/lib/mf/common-styles.js +13 -0
- package/lib/mf/common-styles.js.map +1 -1
- package/lib/mf/static.js +7 -3
- package/lib/mf/static.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/keypad-label.test.jsx +49 -0
- package/src/__tests__/latex-bridge.test.js +87 -1
- package/src/__tests__/mathlive-instance.test.js +20 -0
- package/src/keypad/index.jsx +6 -0
- package/src/latex-bridge.js +128 -3
- package/src/mathlive-instance.js +17 -5
- package/src/mf/common-styles.js +13 -0
- package/src/mf/static.jsx +3 -0
package/package.json
CHANGED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { withVisibleEmptySlots as latexForDisplay } from '../latex-bridge';
|
|
2
|
+
import { keyToAction } from '../latex-bridge';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Keys use empty groups to show a command's shape (`\frac{}{}`, `x^{}`).
|
|
6
|
+
* MathLive renders `{}` as nothing, so labels need a visible stand-in - but the
|
|
7
|
+
* value that gets *inserted* must stay untouched.
|
|
8
|
+
*/
|
|
9
|
+
describe('keypad label display normalisation', () => {
|
|
10
|
+
it('gives empty groups a visible glyph', () => {
|
|
11
|
+
const out = latexForDisplay('\\frac{}{}');
|
|
12
|
+
|
|
13
|
+
expect(out).not.toEqual('\\frac{}{}');
|
|
14
|
+
expect(out).toContain('25AB'); // white small square
|
|
15
|
+
expect(out).toContain('pie-empty');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('handles every empty-group key shape', () => {
|
|
19
|
+
['\\frac{}{}', '\\overline{}', 'x^{}', 'x_{}', '\\sqrt{}', '\\sqrt[{}]{}', '\\abs{}', '\\log_{}', '\\longdiv{}'].forEach(
|
|
20
|
+
(latex) => {
|
|
21
|
+
expect(latexForDisplay(latex)).not.toContain('{}');
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('treats a whitespace-only group as empty', () => {
|
|
27
|
+
// e.g. \frac{x}{ }
|
|
28
|
+
expect(latexForDisplay('\\frac{x}{ }')).toContain('pie-empty');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('leaves latex without empty groups alone', () => {
|
|
32
|
+
['\\pi', '\\sin', 'x^2', '\\overline{AB}'].forEach((latex) => {
|
|
33
|
+
expect(latexForDisplay(latex)).toEqual(latex);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('handles nullish input', () => {
|
|
38
|
+
expect(latexForDisplay(undefined)).toEqual('');
|
|
39
|
+
expect(latexForDisplay('')).toEqual('');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('does NOT affect what gets inserted into the field', () => {
|
|
43
|
+
// the action must use the key's own latex, never the display form
|
|
44
|
+
const key = { name: 'frac', latex: '\\frac{}{}' };
|
|
45
|
+
|
|
46
|
+
expect(keyToAction(key).value).toEqual('\\frac{}{}');
|
|
47
|
+
expect(keyToAction(key).value).not.toContain('pie-empty');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
toMathLive,
|
|
3
|
+
fromMathLive,
|
|
4
|
+
fieldIds,
|
|
5
|
+
keyToAction,
|
|
6
|
+
toNativeCommands,
|
|
7
|
+
NEWLINE_EMBED,
|
|
8
|
+
DEFAULT_FIELD_ID,
|
|
9
|
+
} from '../latex-bridge';
|
|
2
10
|
|
|
3
11
|
describe('latex-bridge', () => {
|
|
4
12
|
describe('toMathLive', () => {
|
|
@@ -64,6 +72,32 @@ describe('latex-bridge', () => {
|
|
|
64
72
|
expect(fromMathLive(toMathLive(stored))).toEqual(stored);
|
|
65
73
|
});
|
|
66
74
|
|
|
75
|
+
// MathLive serialises empty slots as \placeholder{}, which MathJax (used by
|
|
76
|
+
// @pie-lib/math-rendering for prompts/previews) renders as red error text.
|
|
77
|
+
it('strips unnamed placeholders that MathLive adds', () => {
|
|
78
|
+
expect(fromMathLive('\\longdiv{\\placeholder{}}')).toEqual('\\longdiv{}');
|
|
79
|
+
expect(fromMathLive('\\frac{\\placeholder{}}{\\placeholder{}}')).toEqual('\\frac{}{}');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('keeps content inside an unnamed placeholder', () => {
|
|
83
|
+
expect(fromMathLive('\\sqrt{\\placeholder{7}}')).toEqual('\\sqrt{7}');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('handles a bare \\placeholder with no argument', () => {
|
|
87
|
+
expect(fromMathLive('\\longdiv{\\placeholder}')).toEqual('\\longdiv{}');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('never leaks the token \\placeholder', () => {
|
|
91
|
+
['\\longdiv{\\placeholder{}}', '\\frac{\\placeholder{}}{x}', '\\placeholder{}'].forEach((l) => {
|
|
92
|
+
expect(fromMathLive(l)).not.toContain('\\placeholder');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('still maps NAMED placeholders to answer blocks', () => {
|
|
97
|
+
// named ones are answer blocks and must survive
|
|
98
|
+
expect(fromMathLive('\\placeholder[r1]{}')).toEqual('\\MathQuillMathField[r1]{}');
|
|
99
|
+
});
|
|
100
|
+
|
|
67
101
|
it('round-trips answer blocks combined with newlines', () => {
|
|
68
102
|
const stored = `\\MathQuillMathField[r1]{}${NEWLINE_EMBED}y`;
|
|
69
103
|
|
|
@@ -71,6 +105,58 @@ describe('latex-bridge', () => {
|
|
|
71
105
|
});
|
|
72
106
|
});
|
|
73
107
|
|
|
108
|
+
// A MathLive macro serialises from the arguments it was created with, so text
|
|
109
|
+
// typed inside its expansion never comes back out of getValue('latex').
|
|
110
|
+
// Argument-taking pie commands must therefore become native constructs before
|
|
111
|
+
// they reach a mathfield.
|
|
112
|
+
describe('toNativeCommands', () => {
|
|
113
|
+
it('expands \\longdiv to native \\enclose', () => {
|
|
114
|
+
expect(toNativeCommands('\\longdiv{1234}')).toEqual('\\enclose{longdiv}{1234}');
|
|
115
|
+
expect(toNativeCommands('\\longdiv{}')).toEqual('\\enclose{longdiv}{}');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('expands \\overarc to native \\overparen', () => {
|
|
119
|
+
expect(toNativeCommands('\\overarc{AB}')).toEqual('\\overparen{AB}');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('expands \\abs to native \\left|..\\right|', () => {
|
|
123
|
+
expect(toNativeCommands('\\abs{x}')).toEqual('\\left|x\\right|');
|
|
124
|
+
expect(toNativeCommands('\\abs{}')).toEqual('\\left|\\right|');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('handles nested braces inside \\abs', () => {
|
|
128
|
+
expect(toNativeCommands('\\abs{\\frac{1}{2}}')).toEqual('\\left|\\frac{1}{2}\\right|');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('handles several occurrences', () => {
|
|
132
|
+
expect(toNativeCommands('\\abs{a}+\\abs{b}')).toEqual('\\left|a\\right|+\\left|b\\right|');
|
|
133
|
+
expect(toNativeCommands('\\longdiv{1}\\longdiv{2}')).toEqual('\\enclose{longdiv}{1}\\enclose{longdiv}{2}');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('leaves other latex untouched', () => {
|
|
137
|
+
['\\frac{1}{2}', '\\pi', '\\sqrt{x}', '\\overline{AB}'].forEach((l) => {
|
|
138
|
+
expect(toNativeCommands(l)).toEqual(l);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('handles nullish input', () => {
|
|
143
|
+
expect(toNativeCommands(undefined)).toEqual('');
|
|
144
|
+
expect(toNativeCommands('')).toEqual('');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('is applied by toMathLive', () => {
|
|
148
|
+
expect(toMathLive('\\longdiv{5}')).toEqual('\\enclose{longdiv}{5}');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('does not leave a pie macro in what reaches the field', () => {
|
|
152
|
+
['\\longdiv{7}', '\\abs{7}', '\\overarc{7}'].forEach((l) => {
|
|
153
|
+
const out = toMathLive(l);
|
|
154
|
+
|
|
155
|
+
expect(out).not.toMatch(/\\longdiv|\\abs|\\overarc/);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
74
160
|
describe('fieldIds', () => {
|
|
75
161
|
it('returns ids in document order', () => {
|
|
76
162
|
expect(fieldIds('\\MathQuillMathField[r1]{}+\\MathQuillMathField[r2]{}')).toEqual(['r1', 'r2']);
|
|
@@ -63,6 +63,26 @@ describe('mathlive-instance', () => {
|
|
|
63
63
|
expect(macros.longdiv).toBeDefined();
|
|
64
64
|
expect(macros.napprox).toEqual('\\not\\approx');
|
|
65
65
|
});
|
|
66
|
+
|
|
67
|
+
// Argument-taking macros exist only so previously authored latex parses.
|
|
68
|
+
// Editing never goes through them: MathLive serialises a macro from its
|
|
69
|
+
// original arguments, so typed content never comes back out.
|
|
70
|
+
it('argument-taking macros are plain parse-only definitions', () => {
|
|
71
|
+
['longdiv', 'overarc', 'abs'].forEach((name) => {
|
|
72
|
+
expect(typeof instance.PIE_MACROS[name]).toBe('string');
|
|
73
|
+
expect(instance.PIE_MACROS[name]).toContain('#1');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('argument-less symbols stay atomic', () => {
|
|
78
|
+
['perpendicular', 'square', 'degree', 'napprox', 'parallelogram'].forEach((name) => {
|
|
79
|
+
expect(typeof instance.PIE_MACROS[name]).toBe('string');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('longdiv matches the MathJax macro used by math-rendering', () => {
|
|
84
|
+
expect(instance.PIE_MACROS.longdiv).toEqual('\\enclose{longdiv}{#1}');
|
|
85
|
+
});
|
|
66
86
|
});
|
|
67
87
|
|
|
68
88
|
describe('applyStaticMath', () => {
|
package/src/keypad/index.jsx
CHANGED
|
@@ -49,6 +49,12 @@ const LabelHolder = styled('span')(() => ({
|
|
|
49
49
|
'& .ML__placeholder': {
|
|
50
50
|
backgroundColor: color.keypadEmptyPlaceholder ? color.keypadEmptyPlaceholder() : undefined,
|
|
51
51
|
},
|
|
52
|
+
// The stand-in glyph for an empty argument slot (see EMPTY_SLOT). Muted so it
|
|
53
|
+
// reads as "a slot goes here" rather than as part of the symbol, matching how
|
|
54
|
+
// MathQuill shaded `.mq-empty`.
|
|
55
|
+
'& [data-pie-empty]': {
|
|
56
|
+
opacity: 0.5,
|
|
57
|
+
},
|
|
52
58
|
}));
|
|
53
59
|
|
|
54
60
|
export class LatexLabel extends React.Component {
|
package/src/latex-bridge.js
CHANGED
|
@@ -29,9 +29,121 @@ const DISPLAYLINES_REGEX = /^\\displaylines\{([\s\S]*)\}$/;
|
|
|
29
29
|
const MQ_FIELD_REGEX = /\\MathQuillMathField\[([^\]]*)\]\{([^{}]*)\}/g;
|
|
30
30
|
const PLACEHOLDER_REGEX = /\\placeholder\[([^\]]*)\]\{([^{}]*)\}/g;
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Unnamed placeholders, which MathLive inserts on its own.
|
|
34
|
+
*
|
|
35
|
+
* Typing a command with empty arguments (`\longdiv{}`, `\frac{}{}`) makes
|
|
36
|
+
* MathLive serialise the empty slots as `\placeholder{}`. That token is
|
|
37
|
+
* MathLive-specific: MathJax - which @pie-lib/math-rendering uses to render
|
|
38
|
+
* prompts and previews - has no such command and renders it as red error text.
|
|
39
|
+
* Stored latex must therefore keep plain empty groups.
|
|
40
|
+
*/
|
|
41
|
+
const BARE_PLACEHOLDER_REGEX = /\\placeholder\{([^{}]*)\}/g;
|
|
42
|
+
const BARE_PLACEHOLDER_NO_ARG_REGEX = /\\placeholder(?![[{a-zA-Z])/g;
|
|
43
|
+
|
|
32
44
|
/** Default id used when stored latex has an unnamed field. */
|
|
33
45
|
export const DEFAULT_FIELD_ID = 'r1';
|
|
34
46
|
|
|
47
|
+
/**
|
|
48
|
+
* A visible stand-in for an empty argument slot, used for STATIC rendering only.
|
|
49
|
+
*
|
|
50
|
+
* Stored latex legitimately contains empty groups - `\longdiv{}`, `\frac{}{}`,
|
|
51
|
+
* `x^{}` - to express a command's shape. MathQuill drew those as a shaded box
|
|
52
|
+
* (`.mq-empty`); MathLive renders `{}` as nothing, so `convertLatexToMarkup`
|
|
53
|
+
* output comes out completely blank (not even the surrounding bracket, in the
|
|
54
|
+
* case of `\enclose{longdiv}{}`).
|
|
55
|
+
*
|
|
56
|
+
* `\placeholder{}` is not usable here: in static markup it renders as a
|
|
57
|
+
* non-breaking space, because its editable box only exists inside a live
|
|
58
|
+
* mathfield. So an actual glyph is required.
|
|
59
|
+
*
|
|
60
|
+
* Live mathfields need none of this - MathLive manages empty slots itself.
|
|
61
|
+
*/
|
|
62
|
+
const EMPTY_SLOT = '\\htmlData{pie-empty=1}{\\unicode{"25AB}}';
|
|
63
|
+
const EMPTY_GROUP_REGEX = /\{\s*\}/g;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Make empty argument slots visible, for static rendering.
|
|
67
|
+
*
|
|
68
|
+
* Display-only: never feed the result to a mathfield or to storage - the
|
|
69
|
+
* original latex is what gets typed and saved.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} latex
|
|
72
|
+
* @returns {string}
|
|
73
|
+
*/
|
|
74
|
+
export const withVisibleEmptySlots = (latex) => (latex || '').replace(EMPTY_GROUP_REGEX, `{${EMPTY_SLOT}}`);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Pie commands that take an argument, and their native MathLive equivalents.
|
|
78
|
+
*
|
|
79
|
+
* These MUST NOT be handled as MathLive macros. A macro is serialised from the
|
|
80
|
+
* arguments it was created with, so text typed inside its expansion never makes
|
|
81
|
+
* it back out: `getValue('latex')` keeps returning `\longdiv{\placeholder{}}`
|
|
82
|
+
* while `getValue('ascii-math')` shows the typed content. Setting
|
|
83
|
+
* `captureSelection: false` lets the cursor in but does not change
|
|
84
|
+
* serialisation.
|
|
85
|
+
*
|
|
86
|
+
* Expanding to native constructs instead gives real, editable atoms that
|
|
87
|
+
* round-trip. All three targets are understood by MathJax too, so stored latex
|
|
88
|
+
* still renders in @pie-lib/math-rendering without a reverse mapping.
|
|
89
|
+
*/
|
|
90
|
+
/**
|
|
91
|
+
* Rewrite argument-taking pie commands into native MathLive latex.
|
|
92
|
+
* Applied on the way *into* a mathfield.
|
|
93
|
+
*
|
|
94
|
+
* @param {string} latex
|
|
95
|
+
* @returns {string}
|
|
96
|
+
*/
|
|
97
|
+
export const toNativeCommands = (latex) => {
|
|
98
|
+
if (typeof latex !== 'string' || !latex) {
|
|
99
|
+
return latex || '';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let out = latex;
|
|
103
|
+
|
|
104
|
+
// \abs{x} -> \left|x\right| needs brace-aware handling, the rest are prefix
|
|
105
|
+
// swaps.
|
|
106
|
+
out = replaceAbs(out);
|
|
107
|
+
out = out.replace(/\\longdiv\{/g, '\\enclose{longdiv}{');
|
|
108
|
+
out = out.replace(/\\overarc\{/g, '\\overparen{');
|
|
109
|
+
|
|
110
|
+
return out;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/** `\abs{x}` -> `\left|x\right|`, matching balanced braces. */
|
|
114
|
+
const replaceAbs = (latex) => {
|
|
115
|
+
let out = latex;
|
|
116
|
+
let idx = out.indexOf('\\abs{');
|
|
117
|
+
|
|
118
|
+
while (idx !== -1) {
|
|
119
|
+
const open = idx + '\\abs{'.length - 1;
|
|
120
|
+
let depth = 0;
|
|
121
|
+
let close = -1;
|
|
122
|
+
|
|
123
|
+
for (let i = open; i < out.length; i++) {
|
|
124
|
+
if (out[i] === '{') depth++;
|
|
125
|
+
else if (out[i] === '}') {
|
|
126
|
+
depth--;
|
|
127
|
+
if (depth === 0) {
|
|
128
|
+
close = i;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (close === -1) {
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const inner = out.slice(open + 1, close);
|
|
139
|
+
|
|
140
|
+
out = `${out.slice(0, idx)}\\left|${inner}\\right|${out.slice(close + 1)}`;
|
|
141
|
+
idx = out.indexOf('\\abs{');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return out;
|
|
145
|
+
};
|
|
146
|
+
|
|
35
147
|
/**
|
|
36
148
|
* Ids of the answer blocks in a piece of stored latex, in document order.
|
|
37
149
|
*
|
|
@@ -67,8 +179,12 @@ export const toMathLive = (latex) => {
|
|
|
67
179
|
return latex || '';
|
|
68
180
|
}
|
|
69
181
|
|
|
182
|
+
// Argument-taking pie commands -> native constructs, so their content stays
|
|
183
|
+
// editable and serialises back correctly (see toNativeCommands).
|
|
184
|
+
let out = toNativeCommands(latex);
|
|
185
|
+
|
|
70
186
|
// Answer blocks: \MathQuillMathField[id]{x} -> \placeholder[id]{x}
|
|
71
|
-
|
|
187
|
+
out = out.replace(MQ_FIELD_REGEX, (_m, id, content) => `\\placeholder[${id || DEFAULT_FIELD_ID}]{${content}}`);
|
|
72
188
|
|
|
73
189
|
// Newlines: \embed{newLine}[] -> a multiline environment
|
|
74
190
|
if (out.indexOf(NEWLINE_EMBED) !== -1) {
|
|
@@ -108,6 +224,13 @@ export const fromMathLive = (latex) => {
|
|
|
108
224
|
(_m, id, content) => `\\MathQuillMathField[${id || DEFAULT_FIELD_ID}]{${content}}`,
|
|
109
225
|
);
|
|
110
226
|
|
|
227
|
+
// Unnamed placeholders are MathLive's own representation of an empty slot and
|
|
228
|
+
// must not reach storage or MathJax. Unwrap them: keep any content, drop the
|
|
229
|
+
// command itself (the surrounding braces are already part of the host latex,
|
|
230
|
+
// so re-adding them here would produce `\longdiv{{}}`).
|
|
231
|
+
out = out.replace(BARE_PLACEHOLDER_REGEX, (_m, content) => content);
|
|
232
|
+
out = out.replace(BARE_PLACEHOLDER_NO_ARG_REGEX, '');
|
|
233
|
+
|
|
111
234
|
return out;
|
|
112
235
|
};
|
|
113
236
|
|
|
@@ -124,8 +247,10 @@ const COMMANDS_WITH_ARGS = {
|
|
|
124
247
|
'\\overline': '\\overline{#?}',
|
|
125
248
|
'\\overrightarrow': '\\overrightarrow{#?}',
|
|
126
249
|
'\\overleftrightarrow': '\\overleftrightarrow{#?}',
|
|
127
|
-
|
|
128
|
-
|
|
250
|
+
// Native forms, not the pie macros: a macro would not serialise the user's
|
|
251
|
+
// typed content back out (see toNativeCommands).
|
|
252
|
+
'\\overarc': '\\overparen{#?}',
|
|
253
|
+
'\\longdiv': '\\enclose{longdiv}{#?}',
|
|
129
254
|
'^': '^{#?}',
|
|
130
255
|
_: '_{#?}',
|
|
131
256
|
'(': '\\left(#?\\right)',
|
package/src/mathlive-instance.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import debug from 'debug';
|
|
2
|
-
import { NEWLINE_EMBED, toMathLive } from './latex-bridge';
|
|
2
|
+
import { NEWLINE_EMBED, toMathLive, withVisibleEmptySlots } from './latex-bridge';
|
|
3
3
|
|
|
4
4
|
const log = debug('pie-lib:math-input-mathlive:instance');
|
|
5
5
|
|
|
@@ -75,8 +75,9 @@ export const configureFonts = (dir) => {
|
|
|
75
75
|
* way in the editor and in the static renderer.
|
|
76
76
|
*/
|
|
77
77
|
export const PIE_MACROS = {
|
|
78
|
+
// Argument-less symbols. Atomic is correct here: backspace should remove the
|
|
79
|
+
// whole glyph, and there is nothing to type into.
|
|
78
80
|
parallelogram: '\\htmlData{pie-macro=parallelogram}{\\unicode{"25B1}}',
|
|
79
|
-
overarc: '\\overparen{#1}',
|
|
80
81
|
napprox: '\\not\\approx',
|
|
81
82
|
nsim: '\\not\\sim',
|
|
82
83
|
ncong: '\\not\\cong',
|
|
@@ -85,7 +86,15 @@ export const PIE_MACROS = {
|
|
|
85
86
|
divide: '\\div',
|
|
86
87
|
degree: '^{\\circ}',
|
|
87
88
|
square: '\\unicode{"25A1}',
|
|
88
|
-
|
|
89
|
+
|
|
90
|
+
// Argument-taking commands, kept ONLY so previously authored latex still
|
|
91
|
+
// parses and renders. They are never used for editing: MathLive serialises a
|
|
92
|
+
// macro from the arguments it was created with, so text typed inside the
|
|
93
|
+
// expansion never comes back out of `getValue('latex')`. Anything entering a
|
|
94
|
+
// mathfield is rewritten to the native equivalent first - see
|
|
95
|
+
// `toNativeCommands` in latex-bridge.
|
|
96
|
+
longdiv: '\\enclose{longdiv}{#1}',
|
|
97
|
+
overarc: '\\overparen{#1}',
|
|
89
98
|
abs: '\\left|#1\\right|',
|
|
90
99
|
};
|
|
91
100
|
|
|
@@ -347,7 +356,7 @@ export function applyStaticMath(element, latex, options = {}) {
|
|
|
347
356
|
return undefined;
|
|
348
357
|
}
|
|
349
358
|
|
|
350
|
-
element.innerHTML = ml.convertLatexToMarkup(toMathLive(source), {
|
|
359
|
+
element.innerHTML = ml.convertLatexToMarkup(withVisibleEmptySlots(toMathLive(source)), {
|
|
351
360
|
macros: { ...getMacros(), ...(options.macros || {}) },
|
|
352
361
|
});
|
|
353
362
|
|
|
@@ -372,7 +381,10 @@ export function latexToMarkup(latex, options = {}) {
|
|
|
372
381
|
}
|
|
373
382
|
|
|
374
383
|
try {
|
|
375
|
-
|
|
384
|
+
// Empty groups render as nothing in static markup, so give them a visible
|
|
385
|
+
// box. This covers every static path: keypad labels, mf.Static in display
|
|
386
|
+
// mode, and applyStaticMath.
|
|
387
|
+
return ml.convertLatexToMarkup(withVisibleEmptySlots(toMathLive(latex)), {
|
|
376
388
|
macros: { ...getMacros(), ...(options.macros || {}) },
|
|
377
389
|
...options,
|
|
378
390
|
});
|
package/src/mf/common-styles.js
CHANGED
|
@@ -51,6 +51,19 @@ export const placeholderStyles = {
|
|
|
51
51
|
borderBottom: '1px solid currentColor',
|
|
52
52
|
minWidth: '1em',
|
|
53
53
|
},
|
|
54
|
+
// Stand-in glyph for an empty argument slot in STATIC math (see
|
|
55
|
+
// `withVisibleEmptySlots`). Muted so it reads as "a slot goes here" rather
|
|
56
|
+
// than as part of the symbol, matching how MathQuill shaded `.mq-empty`.
|
|
57
|
+
'& [data-pie-empty]': {
|
|
58
|
+
opacity: 0.5,
|
|
59
|
+
},
|
|
60
|
+
// Stretchy accents render an <svg>; mathlive-static.css positions it
|
|
61
|
+
// absolutely at width:100%. Repeated here so static math cannot blow out its
|
|
62
|
+
// container if that stylesheet is missing.
|
|
63
|
+
'& svg': {
|
|
64
|
+
position: 'absolute',
|
|
65
|
+
width: '100%',
|
|
66
|
+
},
|
|
54
67
|
};
|
|
55
68
|
|
|
56
69
|
/**
|
package/src/mf/static.jsx
CHANGED
|
@@ -4,11 +4,14 @@ import { styled } from '@mui/material/styles';
|
|
|
4
4
|
import debug from 'debug';
|
|
5
5
|
import { loadMathLive, getMacros, latexToMarkup } from '../mathlive-instance';
|
|
6
6
|
import { toMathLive, fromMathLive, fieldIds } from '../latex-bridge';
|
|
7
|
+
import { placeholderStyles } from './common-styles';
|
|
7
8
|
|
|
8
9
|
const log = debug('pie-lib:math-input-mathlive:static');
|
|
9
10
|
|
|
10
11
|
const Holder = styled('span')({
|
|
11
12
|
display: 'inline-block',
|
|
13
|
+
// Shared static-math rules: muted empty-slot glyphs and svg containment.
|
|
14
|
+
...placeholderStyles,
|
|
12
15
|
'& math-field': {
|
|
13
16
|
display: 'inline-block',
|
|
14
17
|
border: 'none',
|