@zzish/math-rich-input 0.1.57 → 0.1.59

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zzish/math-rich-input",
3
- "version": "0.1.57",
3
+ "version": "0.1.59",
4
4
  "description": "React component for user to enter rich text with embedded math equations.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,9 +13,13 @@
13
13
  },
14
14
  "main": "dist/index.js",
15
15
  "scripts": {
16
- "test": "playwright test tests/caret-regression.spec.js --workers=1",
16
+ "pretest": "node scripts/build-katex-stylesheet.js",
17
+ "test": "playwright test tests/caret-regression.spec.js tests/math-only-caret.spec.js tests/math-only-caret-react19.spec.js tests/studio-equation.spec.js tests/release-regression.spec.js tests/first-render.spec.js --workers=1",
18
+ "prebuild": "node scripts/build-katex-stylesheet.js",
17
19
  "build": "rollup --config && webpack --config webpack.standalone.config.js",
20
+ "prebuild:legacy": "node scripts/build-katex-stylesheet.js",
18
21
  "build:legacy": "rollup --config",
22
+ "prebuild:standalone": "node scripts/build-katex-stylesheet.js",
19
23
  "build:standalone": "webpack --config webpack.standalone.config.js",
20
24
  "prepublishOnly": "npm test && npm run build"
21
25
  },
@@ -43,6 +47,7 @@
43
47
  "@rollup/plugin-node-resolve": "^11.2.0",
44
48
  "babel-loader": "^8.2.2",
45
49
  "css-loader": "^6.7.1",
50
+ "katex-0.11.1": "npm:katex@0.11.1",
46
51
  "postcss": "^8.4.38",
47
52
  "postcss-loader": "^6.2.1",
48
53
  "postcss-nested": "^6.0.1",
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Build the KaTeX stylesheet the field carries inside itself, so it never fetches one from a CDN.
3
+ *
4
+ * Up to 0.1.58 the field appended `katex@0.11.1`'s stylesheet from jsdelivr on its first mount. Every
5
+ * host's formulas in and around the field have looked the way that stylesheet draws them ever since —
6
+ * Classic's own KaTeX is 0.3.0, two examples have none — so the SAME stylesheet is kept, byte for byte,
7
+ * and only its delivery changes: read from the pinned `katex-0.11.1` dev dependency, with each font face's
8
+ * woff2 inlined as a data URI. A data URI needs no bundler, public path or server in any consumer
9
+ * (webpack 3 and 5, Vite, SvelteKit, plain script tags). The woff and ttf fallbacks are dropped: every
10
+ * browser the hosts support reads woff2 (KaTeX docs, "Kinds of fonts used").
11
+ *
12
+ * Output: `src/katexStylesheet.generated.js` (ignored by git; `prebuild` and `pretest` recreate it).
13
+ */
14
+ const fs = require("node:fs");
15
+ const path = require("node:path");
16
+
17
+ const root = path.resolve(__dirname, "..");
18
+ const dist = path.join(root, "node_modules", "katex-0.11.1", "dist");
19
+ const version = require(path.join(root, "node_modules", "katex-0.11.1", "package.json")).version;
20
+ if (version !== "0.11.1") throw new Error(`expected katex 0.11.1 for the bundled stylesheet, found ${version}`);
21
+
22
+ let missing = 0;
23
+ const css = fs
24
+ .readFileSync(path.join(dist, "katex.min.css"), "utf8")
25
+ .replace(/src:url\(fonts\/([^)]+)\.woff2\) format\("woff2"\)(,url\([^)]*\) format\("[^"]*"\))*/g, (_, name) => {
26
+ const file = path.join(dist, "fonts", `${name}.woff2`);
27
+ if (!fs.existsSync(file)) missing += 1;
28
+ const data = fs.readFileSync(file).toString("base64");
29
+ return `src:url(data:font/woff2;base64,${data}) format("woff2")`;
30
+ });
31
+ if (missing || /url\(fonts\//.test(css)) throw new Error("a KaTeX font was left pointing at a relative URL");
32
+
33
+ const out = path.join(root, "src", "katexStylesheet.generated.js");
34
+ fs.writeFileSync(
35
+ out,
36
+ `// Generated by scripts/build-katex-stylesheet.js from katex@${version}; do not edit.\n` +
37
+ `export const KATEX_VERSION = ${JSON.stringify(version)};\n` +
38
+ `export const KATEX_CSS = ${JSON.stringify(css)};\n`
39
+ );
40
+ console.log(`katex ${version} stylesheet: ${css.length} characters, written to ${path.relative(root, out)}`);
package/src/AccentBar.jsx CHANGED
@@ -728,7 +728,7 @@ export default class AccentBar extends React.Component {
728
728
  key={"accent-button-" + index}
729
729
  onMouseDown={(e) => e.preventDefault()}
730
730
  onMouseUp={(e) => this.handleButtonClick(e, character)}
731
- onClick={(e) => e.preventDefault()}
731
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character); }}
732
732
  >
733
733
  {character}
734
734
  </button>
@@ -786,7 +786,7 @@ export default class AccentBar extends React.Component {
786
786
  onMouseUp={(e) =>
787
787
  this.handleButtonClick(e, character)
788
788
  }
789
- onClick={(e) => e.preventDefault()}
789
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character); }}
790
790
  >
791
791
  {character}
792
792
  </button>
@@ -806,7 +806,7 @@ export default class AccentBar extends React.Component {
806
806
  onMouseUp={(e) =>
807
807
  this.handleButtonClick(e, character, true)
808
808
  }
809
- onClick={(e) => e.preventDefault()}
809
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character, true); }}
810
810
  >
811
811
  {character}
812
812
  </button>
@@ -830,7 +830,7 @@ export default class AccentBar extends React.Component {
830
830
  onMouseUp={(e) =>
831
831
  this.handleButtonClick(e, character, true)
832
832
  }
833
- onClick={(e) => e.preventDefault()}
833
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character, true); }}
834
834
  >
835
835
  {character}
836
836
  </button>
@@ -848,7 +848,7 @@ export default class AccentBar extends React.Component {
848
848
  onMouseUp={(e) =>
849
849
  this.handleButtonClick(e, character, true)
850
850
  }
851
- onClick={(e) => e.preventDefault()}
851
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character, true); }}
852
852
  >
853
853
  {character}
854
854
  </button>
@@ -872,7 +872,7 @@ export default class AccentBar extends React.Component {
872
872
  onMouseUp={(e) =>
873
873
  this.handleButtonClick(e, character, true)
874
874
  }
875
- onClick={(e) => e.preventDefault()}
875
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character, true); }}
876
876
  >
877
877
  {character}
878
878
  </button>
@@ -890,7 +890,7 @@ export default class AccentBar extends React.Component {
890
890
  onMouseUp={(e) =>
891
891
  this.handleButtonClick(e, character, true)
892
892
  }
893
- onClick={(e) => e.preventDefault()}
893
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character, true); }}
894
894
  >
895
895
  {character}
896
896
  </button>
@@ -908,7 +908,7 @@ export default class AccentBar extends React.Component {
908
908
  onMouseUp={(e) =>
909
909
  this.handleButtonClick(e, character, true)
910
910
  }
911
- onClick={(e) => e.preventDefault()}
911
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character, true); }}
912
912
  >
913
913
  {character}
914
914
  </button>
@@ -926,7 +926,7 @@ export default class AccentBar extends React.Component {
926
926
  onMouseUp={(e) =>
927
927
  this.handleButtonClick(e, character, true)
928
928
  }
929
- onClick={(e) => e.preventDefault()}
929
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character, true); }}
930
930
  >
931
931
  {character}
932
932
  </button>
@@ -944,7 +944,7 @@ export default class AccentBar extends React.Component {
944
944
  onMouseUp={(e) =>
945
945
  this.handleButtonClick(e, character, true)
946
946
  }
947
- onClick={(e) => e.preventDefault()}
947
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character, true); }}
948
948
  >
949
949
  {character}
950
950
  </button>
@@ -968,7 +968,7 @@ export default class AccentBar extends React.Component {
968
968
  onMouseUp={(e) =>
969
969
  this.handleButtonClick(e, character, true)
970
970
  }
971
- onClick={(e) => e.preventDefault()}
971
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character, true); }}
972
972
  >
973
973
  {character}
974
974
  </button>
@@ -986,7 +986,7 @@ export default class AccentBar extends React.Component {
986
986
  onMouseUp={(e) =>
987
987
  this.handleButtonClick(e, character, true)
988
988
  }
989
- onClick={(e) => e.preventDefault()}
989
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character, true); }}
990
990
  >
991
991
  {character}
992
992
  </button>
@@ -1004,7 +1004,7 @@ export default class AccentBar extends React.Component {
1004
1004
  onMouseUp={(e) =>
1005
1005
  this.handleButtonClick(e, character, true)
1006
1006
  }
1007
- onClick={(e) => e.preventDefault()}
1007
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character, true); }}
1008
1008
  >
1009
1009
  {character}
1010
1010
  </button>
@@ -1022,7 +1022,7 @@ export default class AccentBar extends React.Component {
1022
1022
  onMouseUp={(e) =>
1023
1023
  this.handleButtonClick(e, character, true)
1024
1024
  }
1025
- onClick={(e) => e.preventDefault()}
1025
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character, true); }}
1026
1026
  >
1027
1027
  {character}
1028
1028
  </button>
@@ -1040,7 +1040,7 @@ export default class AccentBar extends React.Component {
1040
1040
  onMouseUp={(e) =>
1041
1041
  this.handleButtonClick(e, character, true)
1042
1042
  }
1043
- onClick={(e) => e.preventDefault()}
1043
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character, true); }}
1044
1044
  >
1045
1045
  {character}
1046
1046
  </button>
@@ -1058,7 +1058,7 @@ export default class AccentBar extends React.Component {
1058
1058
  onMouseUp={(e) =>
1059
1059
  this.handleButtonClick(e, character, true)
1060
1060
  }
1061
- onClick={(e) => e.preventDefault()}
1061
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character, true); }}
1062
1062
  >
1063
1063
  {character}
1064
1064
  </button>
@@ -1076,7 +1076,7 @@ export default class AccentBar extends React.Component {
1076
1076
  onMouseUp={(e) =>
1077
1077
  this.handleButtonClick(e, character, true)
1078
1078
  }
1079
- onClick={(e) => e.preventDefault()}
1079
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character, true); }}
1080
1080
  >
1081
1081
  {character}
1082
1082
  </button>
@@ -1100,7 +1100,7 @@ export default class AccentBar extends React.Component {
1100
1100
  onMouseUp={(e) =>
1101
1101
  this.handleButtonClick(e, character, true)
1102
1102
  }
1103
- onClick={(e) => e.preventDefault()}
1103
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character, true); }}
1104
1104
  >
1105
1105
  {character}
1106
1106
  </button>
@@ -1118,7 +1118,7 @@ export default class AccentBar extends React.Component {
1118
1118
  onMouseUp={(e) =>
1119
1119
  this.handleButtonClick(e, character, true)
1120
1120
  }
1121
- onClick={(e) => e.preventDefault()}
1121
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character, true); }}
1122
1122
  >
1123
1123
  {character}
1124
1124
  </button>
@@ -1136,7 +1136,7 @@ export default class AccentBar extends React.Component {
1136
1136
  onMouseUp={(e) =>
1137
1137
  this.handleButtonClick(e, character, true)
1138
1138
  }
1139
- onClick={(e) => e.preventDefault()}
1139
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character, true); }}
1140
1140
  >
1141
1141
  {character}
1142
1142
  </button>
@@ -1154,7 +1154,7 @@ export default class AccentBar extends React.Component {
1154
1154
  onMouseUp={(e) =>
1155
1155
  this.handleButtonClick(e, character, true)
1156
1156
  }
1157
- onClick={(e) => e.preventDefault()}
1157
+ onClick={(e) => { e.preventDefault(); if (e.detail === 0) this.handleButtonClick(e, character, true); }}
1158
1158
  >
1159
1159
  {character}
1160
1160
  </button>
@@ -76,12 +76,36 @@ export default class EquationEditor extends React.Component {
76
76
  };
77
77
 
78
78
  this.mathQuill = null;
79
+ // MathQuill's own spelling of the formula the editor was opened with, read once when it mounts.
80
+ this.openedLatexInMathQuill = null;
79
81
  this.handleInsertCallback = props.handleInsert;
80
82
  this.handleCancelCallback = props.handleCancel;
81
83
  }
82
84
 
85
+ /**
86
+ * The latex Insert hands back.
87
+ *
88
+ * Opening a formula makes MathQuill rewrite it in its own spelling (`\frac12` becomes `\frac{1}{2}`,
89
+ * `\int` gains empty limits, `\,` is dropped), and that rewrite reaches state through an edit event
90
+ * although nobody typed. While the content is still exactly that rewrite, or the original, the formula
91
+ * has not been edited, so the original goes back and the host sees no change it did not make.
92
+ */
93
+ latexForInsert() {
94
+ const { latex } = this.state;
95
+ const opened = this.props.latex || "";
96
+ if (opened === "") return latex;
97
+ if (latex === opened) return opened;
98
+ if (
99
+ this.openedLatexInMathQuill !== null &&
100
+ latex === this.openedLatexInMathQuill
101
+ ) {
102
+ return opened;
103
+ }
104
+ return latex;
105
+ }
106
+
83
107
  handleInsert = () => {
84
- this.handleInsertCallback(this.state.latex);
108
+ this.handleInsertCallback(this.latexForInsert());
85
109
  };
86
110
 
87
111
  handleCancel = () => {
@@ -112,8 +136,14 @@ export default class EquationEditor extends React.Component {
112
136
 
113
137
  handleKeyDown = (e) => {
114
138
  try {
139
+ if (e.key === "Escape") {
140
+ e.preventDefault();
141
+ e.stopPropagation();
142
+ this.handleCancelCallback();
143
+ return;
144
+ }
115
145
  if (!this.props.useExpertMode && e.key === "Enter") {
116
- this.handleInsertCallback(this.state.latex);
146
+ this.handleInsertCallback(this.latexForInsert());
117
147
  e.preventDefault();
118
148
  }
119
149
  } catch (error) {
@@ -331,6 +361,15 @@ export default class EquationEditor extends React.Component {
331
361
 
332
362
  mathquillDidMount(mathQuill) {
333
363
  this.mathQuill = mathQuill;
364
+ // Only the first mount, and only while the content is still the formula the editor was opened
365
+ // with: a later mount (after switching modes) may hold the user's own edit, which must never be
366
+ // mistaken for the untouched formula.
367
+ if (
368
+ this.openedLatexInMathQuill === null &&
369
+ this.state.latex === (this.props.latex || "")
370
+ ) {
371
+ this.openedLatexInMathQuill = mathQuill.latex();
372
+ }
334
373
  }
335
374
 
336
375
  componentDidMount() {
@@ -496,7 +535,9 @@ export default class EquationEditor extends React.Component {
496
535
  <div className="centerBox">
497
536
  <EditableMathField
498
537
  className="editable"
499
- latex={this.state.latex}
538
+ // MathQuill rejects TeX's thin-space command and clears the whole formula.
539
+ // Keep the original latex in state; only adapt spacing for its visual parser.
540
+ latex={this.state.latex.replace(/\\,/g, " ")}
500
541
  placeholder="Type equation .."
501
542
  config={{
502
543
  autoOperatorNames: AUTO_OPERATOR_NAMES,
@@ -6,6 +6,9 @@ import "./EquationEditorModal.css";
6
6
 
7
7
  let GLOBAL_lastSelectedExpertMode = false;
8
8
 
9
+ // Anything a host may use as a modal dialog.
10
+ const DIALOG_SELECTOR = '[role="dialog"], [role="alertdialog"], [aria-modal="true"], dialog[open]';
11
+
9
12
  export default class EquationEditorModal extends React.Component {
10
13
  constructor(props) {
11
14
  super(props);
@@ -24,10 +27,80 @@ export default class EquationEditorModal extends React.Component {
24
27
  };
25
28
 
26
29
  handleClose = () => {
30
+ if (this.closing) return;
31
+ this.closing = true;
32
+ if (this.origin?.isConnected) {
33
+ this.origin.focus({ preventScroll: true });
34
+ if (this.originRange && this.origin.contains(this.originRange.commonAncestorContainer)) {
35
+ const selection = window.getSelection();
36
+ selection.removeAllRanges();
37
+ selection.addRange(this.originRange);
38
+ }
39
+ }
27
40
  this.handleCloseCallback();
28
41
  };
29
42
 
43
+ handleKeyDown = (event) => {
44
+ if (event.key !== "Escape") return;
45
+ event.preventDefault();
46
+ event.stopPropagation();
47
+ this.handleClose();
48
+ };
49
+
50
+ /**
51
+ * The dialog's own keyboard boundary.
52
+ *
53
+ * The modal is a portal into document.body, so focus can leave it: a Tab from the last control reaches
54
+ * whatever the host page has behind the overlay, and an Escape pressed out there never comes back here
55
+ * (the handler above only sees events that bubble through the dialog). The capture listener keeps both
56
+ * inside the dialog for as long as it is mounted: Tab wraps, Escape closes, and the originating field and
57
+ * range are restored by handleClose.
58
+ */
59
+ handleDocumentKeyDown = (event) => {
60
+ if (this.closing || !this.mounted) return;
61
+ // A dialog the host opened or revealed over this one owns its own keyboard: Tab stays in it and Escape
62
+ // closes it, not this editor. The host's modal around the field is beneath this editor and does not count.
63
+ if (this.focusIsInLaterDialog()) return;
64
+ if (event.key === "Escape") {
65
+ event.preventDefault();
66
+ event.stopPropagation();
67
+ this.handleClose();
68
+ return;
69
+ }
70
+ if (event.key !== "Tab") return;
71
+ const root = this.modalDiv;
72
+ if (!root) return;
73
+ const focusable = [...root.querySelectorAll(
74
+ 'a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"]), [contenteditable="true"]'
75
+ )].filter((element) => element.checkVisibility?.() ?? true);
76
+ if (focusable.length === 0) return;
77
+ const first = focusable[0], last = focusable[focusable.length - 1];
78
+ const active = document.activeElement;
79
+ const inside = root.contains(active);
80
+ if (!inside) {
81
+ event.preventDefault();
82
+ (event.shiftKey ? last : first).focus({ preventScroll: true });
83
+ return;
84
+ }
85
+ if (!event.shiftKey && active === last) { event.preventDefault(); first.focus({ preventScroll: true }); }
86
+ else if (event.shiftKey && active === first) { event.preventDefault(); last.focus({ preventScroll: true }); }
87
+ };
88
+
89
+ focusIsInLaterDialog() {
90
+ const active = document.activeElement;
91
+ if (!active || !active.closest || !this.modalDiv) return false;
92
+ const dialog = active.closest(DIALOG_SELECTOR);
93
+ if (!dialog || dialog === this.modalDiv || this.modalDiv.contains(dialog)) return false;
94
+ // The host's own dialog around the field this editor was opened from is beneath it. Any other dialog
95
+ // holding focus was opened or revealed over it, including one that was already in the DOM but hidden.
96
+ return !(this.origin && dialog.contains(this.origin));
97
+ }
98
+
30
99
  componentDidMount() {
100
+ this.mounted = true;
101
+ this.origin = document.activeElement;
102
+ const selection = window.getSelection();
103
+ this.originRange = selection?.rangeCount ? selection.getRangeAt(0).cloneRange() : null;
31
104
  // Store current scroll position to prevent jumping
32
105
  this.originalScrollTop =
33
106
  window.pageYOffset || document.documentElement.scrollTop;
@@ -35,6 +108,10 @@ export default class EquationEditorModal extends React.Component {
35
108
  // Create portal container if it doesn't exist
36
109
  this.ensurePortalContainer();
37
110
 
111
+ // Capture phase, so the dialog owns the keyboard even when focus has escaped it to the host page.
112
+ this.documentKeyListener = (event) => this.handleDocumentKeyDown(event);
113
+ document.addEventListener("keydown", this.documentKeyListener, true);
114
+
38
115
  // Lock body scroll to prevent jumping in transform containers
39
116
  this.lockBodyScroll();
40
117
 
@@ -42,6 +119,7 @@ export default class EquationEditorModal extends React.Component {
42
119
  // Need multiple RAF frames for proper CSS animation timing
43
120
  requestAnimationFrame(() => {
44
121
  requestAnimationFrame(() => {
122
+ if (!this.mounted) return;
45
123
  this.setState({ animationReady: true }, () => {
46
124
  // Auto-focus after animation completes (0.3s CSS animation + buffer)
47
125
  setTimeout(() => {
@@ -53,6 +131,11 @@ export default class EquationEditorModal extends React.Component {
53
131
  }
54
132
 
55
133
  componentWillUnmount() {
134
+ this.mounted = false;
135
+ if (this.documentKeyListener) {
136
+ document.removeEventListener("keydown", this.documentKeyListener, true);
137
+ this.documentKeyListener = null;
138
+ }
56
139
  // Hide portal immediately to prevent flash
57
140
  if (this.portalContainer) {
58
141
  this.portalContainer.style.visibility = "hidden";
@@ -145,6 +228,7 @@ export default class EquationEditorModal extends React.Component {
145
228
  }
146
229
 
147
230
  focusEquationEditor = () => {
231
+ if (!this.mounted || this.closing) return;
148
232
  console.log("🔍 Focusing equation editor...");
149
233
  if (this.equationEditor) {
150
234
  console.log("🔍 EquationEditor ref found");
@@ -223,6 +307,10 @@ export default class EquationEditorModal extends React.Component {
223
307
  ></div>
224
308
  <div
225
309
  className={useClass}
310
+ role="dialog"
311
+ aria-modal="true"
312
+ aria-label="Equation Editor"
313
+ onKeyDown={this.handleKeyDown}
226
314
  ref={(r) => (this.modalDiv = r)}
227
315
  style={{ pointerEvents: "auto" }}
228
316
  >
@@ -66,6 +66,8 @@
66
66
 
67
67
  .MathRichInput .katex {
68
68
  border: 0px;
69
+ /* A click opens the equation editor for this formula, so it is a control, not text. */
70
+ cursor: pointer;
69
71
  }
70
72
 
71
73
  .MathRichInput .base {
@@ -90,6 +92,7 @@
90
92
  border-radius: 5px;
91
93
  padding: 2px;
92
94
  margin: 0px 2px;
95
+ cursor: pointer;
93
96
  }
94
97
 
95
98
  .MathRichInput-Container {