@zzish/math-rich-input 0.1.49 → 0.1.51

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.
@@ -1,163 +1,201 @@
1
1
  .EquationEditorModal-background {
2
- position: fixed;
3
- top: 0;
4
- left: 0;
5
- width: 100%;
6
- height: 100%;
7
- background: black;
8
- z-index: 99;
9
- animation: 0.3s shade;
10
- animation-fill-mode: forwards;
2
+ position: fixed;
3
+ top: 0;
4
+ left: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ background: black;
8
+ z-index: 999998;
9
+ /* Start transparent */
10
+ opacity: 0;
11
+ animation: 0.3s shade cubic-bezier(0.2, 0, 0.38, 0.9);
12
+ animation-fill-mode: forwards;
11
13
  }
12
14
 
13
15
  @keyframes shade {
14
- 00% {
15
- opacity: 0;
16
- }
17
- 100% {
18
- opacity: 0.6;
19
- }
16
+ 00% {
17
+ opacity: 0;
18
+ }
19
+ 100% {
20
+ opacity: 0.6;
21
+ }
20
22
  }
21
23
 
22
24
  .EquationEditorModal {
23
- background: white;
24
- border: 1px solid #808080;
25
- border-radius: 10px;
26
- box-shadow: 0px 2px 5px 1px #a0a0a0;
27
- position: fixed;
28
- max-width: 500px;
29
- width: 98%;
30
- top: 50%;
31
- left: 50%;
32
- /* transform: translate(-50%,-50%); */
33
- z-index: 100;
34
- animation: 0.3s rise;
35
- animation-fill-mode: forwards;
25
+ background: white;
26
+ border: 1px solid #808080;
27
+ border-radius: 10px;
28
+ box-shadow: 0px 2px 5px 1px #a0a0a0;
29
+ position: fixed;
30
+ max-width: 500px;
31
+ width: 98%;
32
+ top: 50%;
33
+ left: 50%;
34
+ /* Start invisible to prevent flash, animation will make it visible */
35
+ transform: translate(-50%, 100%);
36
+ opacity: 0;
37
+ z-index: 999999;
38
+ animation: 0.3s rise cubic-bezier(0.2, 0, 0.38, 0.9);
39
+ animation-fill-mode: forwards;
36
40
  }
37
41
 
38
42
  @keyframes rise {
39
- 00% {
40
- transform: translate(-50%, 100%);
41
- opacity: 0;
42
- }
43
- 100% {
44
- transform: translate(-50%, -50%);
45
- opacity: 1;
46
- }
43
+ 00% {
44
+ transform: translate(-50%, 100%);
45
+ opacity: 0;
46
+ }
47
+ 100% {
48
+ transform: translate(-50%, -50%);
49
+ opacity: 1;
50
+ }
51
+ }
52
+
53
+ /* Svelte-friendly alternative: disable animation when needed */
54
+ .EquationEditorModal.no-animation {
55
+ animation: none !important;
56
+ transform: translate(-50%, -50%) !important;
57
+ opacity: 1 !important;
58
+ }
59
+
60
+ .EquationEditorModal-mobile.no-animation {
61
+ animation: none !important;
62
+ transform: translate(-50%, 1px) !important;
63
+ opacity: 1 !important;
64
+ }
65
+
66
+ /* Disable background animation too when modal is no-animation */
67
+ .EquationEditorModal-background.no-animation {
68
+ animation: none !important;
69
+ opacity: 0.6 !important;
47
70
  }
48
71
 
49
72
  .EquationEditorModal-mobile {
50
- background: white;
51
- border: 1px solid #808080;
52
- border-radius: 10px;
53
- box-shadow: 0px 2px 5px 1px #a0a0a0;
54
- position: fixed;
55
- max-width: 500px;
56
- width: 99%;
57
- top: 0%;
58
- left: 50%;
73
+ background: white;
74
+ border: 1px solid #808080;
75
+ border-radius: 10px;
76
+ box-shadow: 0px 2px 5px 1px #a0a0a0;
77
+ position: fixed;
78
+ max-width: 500px;
79
+ width: 99%;
80
+ top: 0%;
81
+ left: 50%;
82
+ /* Start off-screen for animation */
83
+ transform: translate(-50%, -100%);
84
+ opacity: 0;
85
+ z-index: 999999;
86
+ animation: 0.3s riseFromTop cubic-bezier(0.2, 0, 0.38, 0.9);
87
+ animation-fill-mode: forwards;
88
+ }
89
+
90
+ @keyframes riseFromTop {
91
+ 00% {
92
+ transform: translate(-50%, -100%);
93
+ opacity: 0;
94
+ }
95
+ 100% {
59
96
  transform: translate(-50%, 1px);
60
- z-index: 100;
97
+ opacity: 1;
98
+ }
61
99
  }
62
100
 
63
101
  .EquationEditorModal .title {
64
- display: flex;
65
- justify-content: space-between;
66
- font-size: 14px;
67
- color: #404040;
68
- text-align: left;
69
- background: #e0e0e0;
70
- border: 0px;
71
- border-radius: 5px 5px 0px 0px;
72
- padding: 15px;
73
- margin: 0px;
102
+ display: flex;
103
+ justify-content: space-between;
104
+ font-size: 14px;
105
+ color: #404040;
106
+ text-align: left;
107
+ background: #e0e0e0;
108
+ border: 0px;
109
+ border-radius: 5px 5px 0px 0px;
110
+ padding: 15px;
111
+ margin: 0px;
74
112
  }
75
113
 
76
114
  .EquationEditorModal-mobile .title {
77
- display: flex;
78
- justify-content: space-between;
79
- font-size: 14px;
80
- color: #404040;
81
- text-align: left;
82
- background: #e0e0e0;
83
- border: 0px;
84
- border-radius: 5px 5px 0px 0px;
85
- height: 30px;
86
- padding: 15px;
87
- margin: 0px;
115
+ display: flex;
116
+ justify-content: space-between;
117
+ font-size: 14px;
118
+ color: #404040;
119
+ text-align: left;
120
+ background: #e0e0e0;
121
+ border: 0px;
122
+ border-radius: 5px 5px 0px 0px;
123
+ height: 30px;
124
+ padding: 15px;
125
+ margin: 0px;
88
126
  }
89
127
 
90
128
  .expertModeSwitch {
91
- display: flex;
92
- flex-direction: row;
93
- align-items: center;
129
+ display: flex;
130
+ flex-direction: row;
131
+ align-items: center;
94
132
  }
95
133
 
96
134
  .EquationEditorModal .expertModeSwitch .switch {
97
- margin-left: 10px;
135
+ margin-left: 10px;
98
136
  }
99
137
 
100
138
  /* The switch - the box around the slider */
101
139
  .EquationEditorModal .switch {
102
- position: relative;
103
- display: inline-block;
104
- width: 35px;
105
- height: 22px;
106
- margin-bottom: 0;
107
- padding: 0;
140
+ position: relative;
141
+ display: inline-block;
142
+ width: 35px;
143
+ height: 22px;
144
+ margin-bottom: 0;
145
+ padding: 0;
108
146
  }
109
147
 
110
148
  /* Hide default HTML checkbox */
111
149
  .EquationEditorModal .switch input {
112
- opacity: 0;
113
- width: 0;
114
- height: 0;
150
+ opacity: 0;
151
+ width: 0;
152
+ height: 0;
115
153
  }
116
154
 
117
155
  /* The slider */
118
156
  .EquationEditorModal .slider {
119
- position: absolute;
120
- cursor: pointer;
121
- top: 0;
122
- left: 0;
123
- right: 0;
124
- bottom: 0;
125
- background-color: #ccc;
126
- -webkit-transition: 0.4s;
127
- transition: 0.4s;
157
+ position: absolute;
158
+ cursor: pointer;
159
+ top: 0;
160
+ left: 0;
161
+ right: 0;
162
+ bottom: 0;
163
+ background-color: #ccc;
164
+ -webkit-transition: 0.4s;
165
+ transition: 0.4s;
128
166
  }
129
167
 
130
168
  .EquationEditorModal .slider:before {
131
- position: absolute;
132
- content: "";
133
- height: 14px;
134
- width: 14px;
135
- left: 4px;
136
- bottom: 4px;
137
- background-color: white;
138
- -webkit-transition: 0.4s;
139
- transition: 0.4s;
169
+ position: absolute;
170
+ content: "";
171
+ height: 14px;
172
+ width: 14px;
173
+ left: 4px;
174
+ bottom: 4px;
175
+ background-color: white;
176
+ -webkit-transition: 0.4s;
177
+ transition: 0.4s;
140
178
  }
141
179
 
142
180
  .EquationEditorModal input:checked + .slider {
143
- background-color: #663676;
181
+ background-color: #663676;
144
182
  }
145
183
 
146
184
  .EquationEditorModal input:focus + .slider {
147
- box-shadow: 0 0 1px #2196f3;
185
+ box-shadow: 0 0 1px #2196f3;
148
186
  }
149
187
 
150
188
  .EquationEditorModal input:checked + .slider:before {
151
- -webkit-transform: translateX(13px);
152
- -ms-transform: translateX(13px);
153
- transform: translateX(13px);
189
+ -webkit-transform: translateX(13px);
190
+ -ms-transform: translateX(13px);
191
+ transform: translateX(13px);
154
192
  }
155
193
 
156
194
  /* Rounded sliders */
157
195
  .EquationEditorModal .slider.round {
158
- border-radius: 18px;
196
+ border-radius: 18px;
159
197
  }
160
198
 
161
199
  .EquationEditorModal .slider.round:before {
162
- border-radius: 50%;
200
+ border-radius: 50%;
163
201
  }
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import ReactDOM from "react-dom";
2
3
  import EquationEditor from "./EquationEditor.jsx";
3
4
 
4
5
  import "./EquationEditorModal.css";
@@ -11,6 +12,7 @@ export default class EquationEditorModal extends React.Component {
11
12
 
12
13
  this.state = {
13
14
  useExpertMode: GLOBAL_lastSelectedExpertMode,
15
+ animationReady: false,
14
16
  };
15
17
 
16
18
  this.handleCloseCallback = props.handleClose;
@@ -26,14 +28,114 @@ export default class EquationEditorModal extends React.Component {
26
28
  };
27
29
 
28
30
  componentDidMount() {
29
- // Auto-focus when modal opens
30
- // Use setTimeout to ensure child components are fully mounted
31
- setTimeout(() => {
32
- this.focusEquationEditor();
33
- }, 100);
31
+ // Store current scroll position to prevent jumping
32
+ this.originalScrollTop =
33
+ window.pageYOffset || document.documentElement.scrollTop;
34
+
35
+ // Create portal container if it doesn't exist
36
+ this.ensurePortalContainer();
37
+
38
+ // Lock body scroll to prevent jumping in transform containers
39
+ this.lockBodyScroll();
40
+
41
+ // Enable animation after portal is ready (ensures smooth slide-in)
42
+ // Need multiple RAF frames for proper CSS animation timing
43
+ requestAnimationFrame(() => {
44
+ requestAnimationFrame(() => {
45
+ this.setState({ animationReady: true }, () => {
46
+ // Auto-focus after animation completes (0.3s CSS animation + buffer)
47
+ setTimeout(() => {
48
+ this.focusEquationEditor();
49
+ }, 400); // Wait for animation to complete
50
+ });
51
+ });
52
+ });
53
+ }
54
+
55
+ componentWillUnmount() {
56
+ // Hide portal immediately to prevent flash
57
+ if (this.portalContainer) {
58
+ this.portalContainer.style.visibility = "hidden";
59
+ }
60
+
61
+ // Unlock body scroll and restore original scroll position
62
+ this.unlockBodyScroll();
63
+
64
+ // Clean up portal container after a small delay
65
+ if (this.createdPortalContainer && this.portalContainer) {
66
+ setTimeout(() => {
67
+ if (this.portalContainer && this.portalContainer.parentNode) {
68
+ document.body.removeChild(this.portalContainer);
69
+ }
70
+ }, 100);
71
+ }
72
+ }
73
+
74
+ lockBodyScroll() {
75
+ // Store original overflow styles and scroll position
76
+ this.originalBodyOverflow = document.body.style.overflow;
77
+ this.originalDocumentOverflow = document.documentElement.style.overflow;
78
+ this.originalBodyPosition = document.body.style.position;
79
+ this.originalBodyTop = document.body.style.top;
80
+ this.originalBodyWidth = document.body.style.width;
81
+
82
+ // Get current scroll position
83
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
84
+
85
+ // Apply position: fixed to prevent scrolling while preserving position
86
+ document.body.style.position = "fixed";
87
+ document.body.style.top = `-${scrollTop}px`;
88
+ document.body.style.width = "100%";
89
+ document.body.style.overflow = "hidden";
90
+ }
91
+
92
+ unlockBodyScroll() {
93
+ // Get the scroll position from the fixed top value
94
+ const scrollTop = Math.abs(parseInt(document.body.style.top) || 0);
95
+
96
+ // Restore original styles
97
+ document.body.style.position = this.originalBodyPosition || "";
98
+ document.body.style.top = this.originalBodyTop || "";
99
+ document.body.style.width = this.originalBodyWidth || "";
100
+ document.body.style.overflow = this.originalBodyOverflow || "";
101
+ document.documentElement.style.overflow =
102
+ this.originalDocumentOverflow || "";
103
+
104
+ // Restore scroll position smoothly
105
+ if (scrollTop > 0) {
106
+ window.scrollTo(0, scrollTop);
107
+ }
108
+ }
109
+
110
+ ensurePortalContainer() {
111
+ // Look for existing portal container
112
+ this.portalContainer = document.getElementById(
113
+ "math-rich-input-modal-portal"
114
+ );
115
+
116
+ if (!this.portalContainer) {
117
+ // Create new portal container
118
+ this.portalContainer = document.createElement("div");
119
+ this.portalContainer.id = "math-rich-input-modal-portal";
120
+ // Portal container should not interfere with content positioning
121
+ // Start invisible until animation is ready
122
+ this.portalContainer.style.cssText =
123
+ "position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 999990; visibility: hidden;";
124
+ document.body.appendChild(this.portalContainer);
125
+ this.createdPortalContainer = true;
126
+ }
34
127
  }
35
128
 
36
129
  componentDidUpdate(prevProps, prevState) {
130
+ // Show portal container when animation is ready
131
+ if (
132
+ !prevState.animationReady &&
133
+ this.state.animationReady &&
134
+ this.portalContainer
135
+ ) {
136
+ this.portalContainer.style.visibility = "visible";
137
+ }
138
+
37
139
  // Re-focus when expert mode changes
38
140
  if (prevState.useExpertMode !== this.state.useExpertMode) {
39
141
  setTimeout(() => {
@@ -46,25 +148,44 @@ export default class EquationEditorModal extends React.Component {
46
148
  console.log("🔍 Focusing equation editor...");
47
149
  if (this.equationEditor) {
48
150
  console.log("🔍 EquationEditor ref found");
49
- if (this.state.useExpertMode) {
50
- // Focus textarea in expert mode
51
- console.log("🔍 Expert mode - focusing latexInput");
52
- if (this.equationEditor.latexInput) {
53
- this.equationEditor.latexInput.focus();
54
- console.log("✅ LatexInput focused");
55
- } else {
56
- console.log("❌ LatexInput not found");
57
- }
58
- } else {
59
- // Focus MathQuill in normal mode
60
- console.log("🔍 Normal mode - focusing mathQuill");
61
- if (this.equationEditor.mathQuill) {
62
- this.equationEditor.mathQuill.focus();
63
- console.log(" MathQuill focused");
151
+
152
+ // Prevent scroll jumping during focus
153
+ const currentScrollTop =
154
+ window.pageYOffset || document.documentElement.scrollTop;
155
+
156
+ try {
157
+ if (this.state.useExpertMode) {
158
+ // Focus textarea in expert mode
159
+ console.log("🔍 Expert mode - focusing latexInput");
160
+ if (this.equationEditor.latexInput) {
161
+ // Use preventScroll option if available
162
+ this.equationEditor.latexInput.focus({ preventScroll: true });
163
+ console.log("✅ LatexInput focused");
164
+ } else {
165
+ console.log(" LatexInput not found");
166
+ }
64
167
  } else {
65
- console.log("❌ MathQuill not found");
168
+ // Focus MathQuill in normal mode
169
+ console.log("🔍 Normal mode - focusing mathQuill");
170
+ if (this.equationEditor.mathQuill) {
171
+ this.equationEditor.mathQuill.focus();
172
+ console.log("✅ MathQuill focused");
173
+ } else {
174
+ console.log("❌ MathQuill not found");
175
+ }
66
176
  }
177
+ } catch (error) {
178
+ console.warn("🔍 Focus failed:", error);
67
179
  }
180
+
181
+ // Ensure scroll position hasn't changed
182
+ setTimeout(() => {
183
+ const newScrollTop =
184
+ window.pageYOffset || document.documentElement.scrollTop;
185
+ if (newScrollTop !== currentScrollTop) {
186
+ window.scrollTo(0, currentScrollTop);
187
+ }
188
+ }, 10);
68
189
  } else {
69
190
  console.log("❌ EquationEditor ref not found");
70
191
  }
@@ -86,13 +207,25 @@ export default class EquationEditorModal extends React.Component {
86
207
  let useClass = "EquationEditorModal";
87
208
  if (this.isMobile()) useClass = "EquationEditorModal-mobile";
88
209
 
89
- return (
210
+ // Add support for disabling animations (useful for Svelte transitions)
211
+ if (this.props.disableAnimation) {
212
+ useClass += " no-animation";
213
+ }
214
+
215
+ const modalContent = (
90
216
  <>
91
217
  <div
92
- className="EquationEditorModal-background"
218
+ className={`EquationEditorModal-background${
219
+ this.props.disableAnimation ? " no-animation" : ""
220
+ }`}
93
221
  onClick={this.handleClose}
222
+ style={{ pointerEvents: "auto" }}
94
223
  ></div>
95
- <div className={useClass} ref={(r) => (this.modalDiv = r)}>
224
+ <div
225
+ className={useClass}
226
+ ref={(r) => (this.modalDiv = r)}
227
+ style={{ pointerEvents: "auto" }}
228
+ >
96
229
  {!this.isMobile() && (
97
230
  <div className="title">
98
231
  Equation Editor
@@ -120,5 +253,13 @@ export default class EquationEditorModal extends React.Component {
120
253
  </div>
121
254
  </>
122
255
  );
256
+
257
+ // Use portal to render modal outside of parent container stacking context
258
+ if (this.portalContainer && this.state.animationReady) {
259
+ return ReactDOM.createPortal(modalContent, this.portalContainer);
260
+ }
261
+
262
+ // Show nothing until portal and animation are ready (prevents flash)
263
+ return null;
123
264
  }
124
265
  }
@@ -3115,6 +3115,7 @@ export default class MathRichInput extends React.Component {
3115
3115
  handleClose={this.handleEquationEditorClose}
3116
3116
  handleInsert={this.handleEquationEditorInsert}
3117
3117
  latex={this.state.equationEditorLatex}
3118
+ disableAnimation={this.props.disableAnimation}
3118
3119
  />
3119
3120
  )}
3120
3121
  </>
@@ -23,6 +23,7 @@
23
23
  padding: 1px;
24
24
  margin: 3px;
25
25
  margin-bottom: auto;
26
+ cursor: pointer;
26
27
  /* transition: all .2s ease-in-out; */
27
28
  }
28
29