@zzish/math-rich-input 0.1.57 → 0.1.58
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/dist/index.js +469 -77
- package/dist/math-rich-input.css +1 -1
- package/dist/standalone.js +1 -1
- package/package.json +2 -2
- package/src/AccentBar.jsx +21 -21
- package/src/EquationEditor.jsx +44 -3
- package/src/EquationEditorModal.jsx +88 -0
- package/src/MathRichInput.css +3 -0
- package/src/MathRichInput.jsx +269 -29
- package/src/Toolbar.jsx +29 -15
- package/src/mathRichInputHelper.js +7 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zzish/math-rich-input",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.58",
|
|
4
4
|
"description": "React component for user to enter rich text with embedded math equations.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"main": "dist/index.js",
|
|
15
15
|
"scripts": {
|
|
16
|
-
"test": "playwright test tests/caret-regression.spec.js --workers=1",
|
|
16
|
+
"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 --workers=1",
|
|
17
17
|
"build": "rollup --config && webpack --config webpack.standalone.config.js",
|
|
18
18
|
"build:legacy": "rollup --config",
|
|
19
19
|
"build:standalone": "webpack --config webpack.standalone.config.js",
|
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>
|
package/src/EquationEditor.jsx
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
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
|
>
|
package/src/MathRichInput.css
CHANGED
|
@@ -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 {
|