@zzish/math-rich-input 0.1.56 → 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 CHANGED
@@ -169,6 +169,57 @@ function _arrayLikeToArray(arr, len) {
169
169
  function _nonIterableSpread() {
170
170
  throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
171
171
  }
172
+ function _createForOfIteratorHelper(o, allowArrayLike) {
173
+ var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
174
+ if (!it) {
175
+ if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
176
+ if (it) o = it;
177
+ var i = 0;
178
+ var F = function () {};
179
+ return {
180
+ s: F,
181
+ n: function () {
182
+ if (i >= o.length) return {
183
+ done: true
184
+ };
185
+ return {
186
+ done: false,
187
+ value: o[i++]
188
+ };
189
+ },
190
+ e: function (e) {
191
+ throw e;
192
+ },
193
+ f: F
194
+ };
195
+ }
196
+ throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
197
+ }
198
+ var normalCompletion = true,
199
+ didErr = false,
200
+ err;
201
+ return {
202
+ s: function () {
203
+ it = it.call(o);
204
+ },
205
+ n: function () {
206
+ var step = it.next();
207
+ normalCompletion = step.done;
208
+ return step;
209
+ },
210
+ e: function (e) {
211
+ didErr = true;
212
+ err = e;
213
+ },
214
+ f: function () {
215
+ try {
216
+ if (!normalCompletion && it.return != null) it.return();
217
+ } finally {
218
+ if (didErr) throw err;
219
+ }
220
+ }
221
+ };
222
+ }
172
223
 
173
224
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
174
225
 
@@ -18835,7 +18886,7 @@ var EquationEditor = /*#__PURE__*/function (_React$Component) {
18835
18886
  _classCallCheck(this, EquationEditor);
18836
18887
  _this = _callSuper(this, EquationEditor, [props]);
18837
18888
  _defineProperty(_this, "handleInsert", function () {
18838
- _this.handleInsertCallback(_this.state.latex);
18889
+ _this.handleInsertCallback(_this.latexForInsert());
18839
18890
  });
18840
18891
  _defineProperty(_this, "handleCancel", function () {
18841
18892
  _this.handleCancelCallback();
@@ -18848,8 +18899,14 @@ var EquationEditor = /*#__PURE__*/function (_React$Component) {
18848
18899
  });
18849
18900
  _defineProperty(_this, "handleKeyDown", function (e) {
18850
18901
  try {
18902
+ if (e.key === "Escape") {
18903
+ e.preventDefault();
18904
+ e.stopPropagation();
18905
+ _this.handleCancelCallback();
18906
+ return;
18907
+ }
18851
18908
  if (!_this.props.useExpertMode && e.key === "Enter") {
18852
- _this.handleInsertCallback(_this.state.latex);
18909
+ _this.handleInsertCallback(_this.latexForInsert());
18853
18910
  e.preventDefault();
18854
18911
  }
18855
18912
  } catch (error) {
@@ -19022,12 +19079,35 @@ var EquationEditor = /*#__PURE__*/function (_React$Component) {
19022
19079
  recentSymbolsCount: 0
19023
19080
  };
19024
19081
  _this.mathQuill = null;
19082
+ // MathQuill's own spelling of the formula the editor was opened with, read once when it mounts.
19083
+ _this.openedLatexInMathQuill = null;
19025
19084
  _this.handleInsertCallback = props.handleInsert;
19026
19085
  _this.handleCancelCallback = props.handleCancel;
19027
19086
  return _this;
19028
19087
  }
19088
+
19089
+ /**
19090
+ * The latex Insert hands back.
19091
+ *
19092
+ * Opening a formula makes MathQuill rewrite it in its own spelling (`\frac12` becomes `\frac{1}{2}`,
19093
+ * `\int` gains empty limits, `\,` is dropped), and that rewrite reaches state through an edit event
19094
+ * although nobody typed. While the content is still exactly that rewrite, or the original, the formula
19095
+ * has not been edited, so the original goes back and the host sees no change it did not make.
19096
+ */
19029
19097
  _inherits(EquationEditor, _React$Component);
19030
19098
  return _createClass(EquationEditor, [{
19099
+ key: "latexForInsert",
19100
+ value: function latexForInsert() {
19101
+ var latex = this.state.latex;
19102
+ var opened = this.props.latex || "";
19103
+ if (opened === "") return latex;
19104
+ if (latex === opened) return opened;
19105
+ if (this.openedLatexInMathQuill !== null && latex === this.openedLatexInMathQuill) {
19106
+ return opened;
19107
+ }
19108
+ return latex;
19109
+ }
19110
+ }, {
19031
19111
  key: "prettifyLatex",
19032
19112
  value: function prettifyLatex(latex) {
19033
19113
  // NB: replaceAll not suported on safari
@@ -19109,6 +19189,12 @@ var EquationEditor = /*#__PURE__*/function (_React$Component) {
19109
19189
  key: "mathquillDidMount",
19110
19190
  value: function mathquillDidMount(mathQuill) {
19111
19191
  this.mathQuill = mathQuill;
19192
+ // Only the first mount, and only while the content is still the formula the editor was opened
19193
+ // with: a later mount (after switching modes) may hold the user's own edit, which must never be
19194
+ // mistaken for the untouched formula.
19195
+ if (this.openedLatexInMathQuill === null && this.state.latex === (this.props.latex || "")) {
19196
+ this.openedLatexInMathQuill = mathQuill.latex();
19197
+ }
19112
19198
  }
19113
19199
  }, {
19114
19200
  key: "componentDidMount",
@@ -19215,8 +19301,11 @@ var EquationEditor = /*#__PURE__*/function (_React$Component) {
19215
19301
  }, /*#__PURE__*/React__default["default"].createElement("div", {
19216
19302
  className: "centerBox"
19217
19303
  }, /*#__PURE__*/React__default["default"].createElement(reactMathquill.EditableMathField, {
19218
- className: "editable",
19219
- latex: this.state.latex,
19304
+ className: "editable"
19305
+ // MathQuill rejects TeX's thin-space command and clears the whole formula.
19306
+ // Keep the original latex in state; only adapt spacing for its visual parser.
19307
+ ,
19308
+ latex: this.state.latex.replace(/\\,/g, " "),
19220
19309
  placeholder: "Type equation ..",
19221
19310
  config: {
19222
19311
  autoOperatorNames: AUTO_OPERATOR_NAMES
@@ -19298,6 +19387,9 @@ var EquationEditor = /*#__PURE__*/function (_React$Component) {
19298
19387
  }(React__default["default"].Component);
19299
19388
 
19300
19389
  var GLOBAL_lastSelectedExpertMode = false;
19390
+
19391
+ // Anything a host may use as a modal dialog.
19392
+ var DIALOG_SELECTOR = '[role="dialog"], [role="alertdialog"], [aria-modal="true"], dialog[open]';
19301
19393
  var EquationEditorModal = /*#__PURE__*/function (_React$Component) {
19302
19394
  function EquationEditorModal(props) {
19303
19395
  var _this;
@@ -19307,9 +19399,80 @@ var EquationEditorModal = /*#__PURE__*/function (_React$Component) {
19307
19399
  _this.handleInsertCallback(latex);
19308
19400
  });
19309
19401
  _defineProperty(_this, "handleClose", function () {
19402
+ var _this$origin;
19403
+ if (_this.closing) return;
19404
+ _this.closing = true;
19405
+ if ((_this$origin = _this.origin) !== null && _this$origin !== void 0 && _this$origin.isConnected) {
19406
+ _this.origin.focus({
19407
+ preventScroll: true
19408
+ });
19409
+ if (_this.originRange && _this.origin.contains(_this.originRange.commonAncestorContainer)) {
19410
+ var selection = window.getSelection();
19411
+ selection.removeAllRanges();
19412
+ selection.addRange(_this.originRange);
19413
+ }
19414
+ }
19310
19415
  _this.handleCloseCallback();
19311
19416
  });
19417
+ _defineProperty(_this, "handleKeyDown", function (event) {
19418
+ if (event.key !== "Escape") return;
19419
+ event.preventDefault();
19420
+ event.stopPropagation();
19421
+ _this.handleClose();
19422
+ });
19423
+ /**
19424
+ * The dialog's own keyboard boundary.
19425
+ *
19426
+ * The modal is a portal into document.body, so focus can leave it: a Tab from the last control reaches
19427
+ * whatever the host page has behind the overlay, and an Escape pressed out there never comes back here
19428
+ * (the handler above only sees events that bubble through the dialog). The capture listener keeps both
19429
+ * inside the dialog for as long as it is mounted: Tab wraps, Escape closes, and the originating field and
19430
+ * range are restored by handleClose.
19431
+ */
19432
+ _defineProperty(_this, "handleDocumentKeyDown", function (event) {
19433
+ if (_this.closing || !_this.mounted) return;
19434
+ // A dialog the host opened or revealed over this one owns its own keyboard: Tab stays in it and Escape
19435
+ // closes it, not this editor. The host's modal around the field is beneath this editor and does not count.
19436
+ if (_this.focusIsInLaterDialog()) return;
19437
+ if (event.key === "Escape") {
19438
+ event.preventDefault();
19439
+ event.stopPropagation();
19440
+ _this.handleClose();
19441
+ return;
19442
+ }
19443
+ if (event.key !== "Tab") return;
19444
+ var root = _this.modalDiv;
19445
+ if (!root) return;
19446
+ var focusable = _toConsumableArray(root.querySelectorAll('a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"]), [contenteditable="true"]')).filter(function (element) {
19447
+ var _element$checkVisibil, _element$checkVisibil2;
19448
+ return (_element$checkVisibil = (_element$checkVisibil2 = element.checkVisibility) === null || _element$checkVisibil2 === void 0 ? void 0 : _element$checkVisibil2.call(element)) !== null && _element$checkVisibil !== void 0 ? _element$checkVisibil : true;
19449
+ });
19450
+ if (focusable.length === 0) return;
19451
+ var first = focusable[0],
19452
+ last = focusable[focusable.length - 1];
19453
+ var active = document.activeElement;
19454
+ var inside = root.contains(active);
19455
+ if (!inside) {
19456
+ event.preventDefault();
19457
+ (event.shiftKey ? last : first).focus({
19458
+ preventScroll: true
19459
+ });
19460
+ return;
19461
+ }
19462
+ if (!event.shiftKey && active === last) {
19463
+ event.preventDefault();
19464
+ first.focus({
19465
+ preventScroll: true
19466
+ });
19467
+ } else if (event.shiftKey && active === first) {
19468
+ event.preventDefault();
19469
+ last.focus({
19470
+ preventScroll: true
19471
+ });
19472
+ }
19473
+ });
19312
19474
  _defineProperty(_this, "focusEquationEditor", function () {
19475
+ if (!_this.mounted || _this.closing) return;
19313
19476
  console.log("🔍 Focusing equation editor...");
19314
19477
  if (_this.equationEditor) {
19315
19478
  console.log("🔍 EquationEditor ref found");
@@ -19364,15 +19527,36 @@ var EquationEditorModal = /*#__PURE__*/function (_React$Component) {
19364
19527
  }
19365
19528
  _inherits(EquationEditorModal, _React$Component);
19366
19529
  return _createClass(EquationEditorModal, [{
19530
+ key: "focusIsInLaterDialog",
19531
+ value: function focusIsInLaterDialog() {
19532
+ var active = document.activeElement;
19533
+ if (!active || !active.closest || !this.modalDiv) return false;
19534
+ var dialog = active.closest(DIALOG_SELECTOR);
19535
+ if (!dialog || dialog === this.modalDiv || this.modalDiv.contains(dialog)) return false;
19536
+ // The host's own dialog around the field this editor was opened from is beneath it. Any other dialog
19537
+ // holding focus was opened or revealed over it, including one that was already in the DOM but hidden.
19538
+ return !(this.origin && dialog.contains(this.origin));
19539
+ }
19540
+ }, {
19367
19541
  key: "componentDidMount",
19368
19542
  value: function componentDidMount() {
19369
19543
  var _this2 = this;
19544
+ this.mounted = true;
19545
+ this.origin = document.activeElement;
19546
+ var selection = window.getSelection();
19547
+ this.originRange = selection !== null && selection !== void 0 && selection.rangeCount ? selection.getRangeAt(0).cloneRange() : null;
19370
19548
  // Store current scroll position to prevent jumping
19371
19549
  this.originalScrollTop = window.pageYOffset || document.documentElement.scrollTop;
19372
19550
 
19373
19551
  // Create portal container if it doesn't exist
19374
19552
  this.ensurePortalContainer();
19375
19553
 
19554
+ // Capture phase, so the dialog owns the keyboard even when focus has escaped it to the host page.
19555
+ this.documentKeyListener = function (event) {
19556
+ return _this2.handleDocumentKeyDown(event);
19557
+ };
19558
+ document.addEventListener("keydown", this.documentKeyListener, true);
19559
+
19376
19560
  // Lock body scroll to prevent jumping in transform containers
19377
19561
  this.lockBodyScroll();
19378
19562
 
@@ -19380,6 +19564,7 @@ var EquationEditorModal = /*#__PURE__*/function (_React$Component) {
19380
19564
  // Need multiple RAF frames for proper CSS animation timing
19381
19565
  requestAnimationFrame(function () {
19382
19566
  requestAnimationFrame(function () {
19567
+ if (!_this2.mounted) return;
19383
19568
  _this2.setState({
19384
19569
  animationReady: true
19385
19570
  }, function () {
@@ -19395,6 +19580,11 @@ var EquationEditorModal = /*#__PURE__*/function (_React$Component) {
19395
19580
  key: "componentWillUnmount",
19396
19581
  value: function componentWillUnmount() {
19397
19582
  var _this3 = this;
19583
+ this.mounted = false;
19584
+ if (this.documentKeyListener) {
19585
+ document.removeEventListener("keydown", this.documentKeyListener, true);
19586
+ this.documentKeyListener = null;
19587
+ }
19398
19588
  // Hide portal immediately to prevent flash
19399
19589
  if (this.portalContainer) {
19400
19590
  this.portalContainer.style.visibility = "hidden";
@@ -19513,6 +19703,10 @@ var EquationEditorModal = /*#__PURE__*/function (_React$Component) {
19513
19703
  }
19514
19704
  }), /*#__PURE__*/React__default["default"].createElement("div", {
19515
19705
  className: useClass,
19706
+ role: "dialog",
19707
+ "aria-modal": "true",
19708
+ "aria-label": "Equation Editor",
19709
+ onKeyDown: this.handleKeyDown,
19516
19710
  ref: function ref(r) {
19517
19711
  return _this5.modalDiv = r;
19518
19712
  },
@@ -19870,7 +20064,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
19870
20064
  return _this2.handleButtonClick(e, character);
19871
20065
  },
19872
20066
  onClick: function onClick(e) {
19873
- return e.preventDefault();
20067
+ e.preventDefault();
20068
+ if (e.detail === 0) _this2.handleButtonClick(e, character);
19874
20069
  }
19875
20070
  }, character);
19876
20071
  })), !compactMode && /*#__PURE__*/React__default["default"].createElement("div", null, /*#__PURE__*/React__default["default"].createElement("div", {
@@ -19916,7 +20111,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
19916
20111
  return _this2.handleButtonClick(e, character);
19917
20112
  },
19918
20113
  onClick: function onClick(e) {
19919
- return e.preventDefault();
20114
+ e.preventDefault();
20115
+ if (e.detail === 0) _this2.handleButtonClick(e, character);
19920
20116
  }
19921
20117
  }, character);
19922
20118
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -19934,7 +20130,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
19934
20130
  return _this2.handleButtonClick(e, character, true);
19935
20131
  },
19936
20132
  onClick: function onClick(e) {
19937
- return e.preventDefault();
20133
+ e.preventDefault();
20134
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
19938
20135
  }
19939
20136
  }, character);
19940
20137
  }))), this.state.selectedTab === "Greek" && /*#__PURE__*/React__default["default"].createElement("div", {
@@ -19954,7 +20151,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
19954
20151
  return _this2.handleButtonClick(e, character, true);
19955
20152
  },
19956
20153
  onClick: function onClick(e) {
19957
- return e.preventDefault();
20154
+ e.preventDefault();
20155
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
19958
20156
  }
19959
20157
  }, character);
19960
20158
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -19972,7 +20170,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
19972
20170
  return _this2.handleButtonClick(e, character, true);
19973
20171
  },
19974
20172
  onClick: function onClick(e) {
19975
- return e.preventDefault();
20173
+ e.preventDefault();
20174
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
19976
20175
  }
19977
20176
  }, character);
19978
20177
  }))), this.state.selectedTab === "Math" && /*#__PURE__*/React__default["default"].createElement("div", {
@@ -19992,7 +20191,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
19992
20191
  return _this2.handleButtonClick(e, character, true);
19993
20192
  },
19994
20193
  onClick: function onClick(e) {
19995
- return e.preventDefault();
20194
+ e.preventDefault();
20195
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
19996
20196
  }
19997
20197
  }, character);
19998
20198
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20010,7 +20210,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20010
20210
  return _this2.handleButtonClick(e, character, true);
20011
20211
  },
20012
20212
  onClick: function onClick(e) {
20013
- return e.preventDefault();
20213
+ e.preventDefault();
20214
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20014
20215
  }
20015
20216
  }, character);
20016
20217
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20028,7 +20229,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20028
20229
  return _this2.handleButtonClick(e, character, true);
20029
20230
  },
20030
20231
  onClick: function onClick(e) {
20031
- return e.preventDefault();
20232
+ e.preventDefault();
20233
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20032
20234
  }
20033
20235
  }, character);
20034
20236
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20046,7 +20248,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20046
20248
  return _this2.handleButtonClick(e, character, true);
20047
20249
  },
20048
20250
  onClick: function onClick(e) {
20049
- return e.preventDefault();
20251
+ e.preventDefault();
20252
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20050
20253
  }
20051
20254
  }, character);
20052
20255
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20064,7 +20267,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20064
20267
  return _this2.handleButtonClick(e, character, true);
20065
20268
  },
20066
20269
  onClick: function onClick(e) {
20067
- return e.preventDefault();
20270
+ e.preventDefault();
20271
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20068
20272
  }
20069
20273
  }, character);
20070
20274
  }))), this.state.selectedTab === "Emoticons" && /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20084,7 +20288,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20084
20288
  return _this2.handleButtonClick(e, character, true);
20085
20289
  },
20086
20290
  onClick: function onClick(e) {
20087
- return e.preventDefault();
20291
+ e.preventDefault();
20292
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20088
20293
  }
20089
20294
  }, character);
20090
20295
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20102,7 +20307,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20102
20307
  return _this2.handleButtonClick(e, character, true);
20103
20308
  },
20104
20309
  onClick: function onClick(e) {
20105
- return e.preventDefault();
20310
+ e.preventDefault();
20311
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20106
20312
  }
20107
20313
  }, character);
20108
20314
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20120,7 +20326,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20120
20326
  return _this2.handleButtonClick(e, character, true);
20121
20327
  },
20122
20328
  onClick: function onClick(e) {
20123
- return e.preventDefault();
20329
+ e.preventDefault();
20330
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20124
20331
  }
20125
20332
  }, character);
20126
20333
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20138,7 +20345,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20138
20345
  return _this2.handleButtonClick(e, character, true);
20139
20346
  },
20140
20347
  onClick: function onClick(e) {
20141
- return e.preventDefault();
20348
+ e.preventDefault();
20349
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20142
20350
  }
20143
20351
  }, character);
20144
20352
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20156,7 +20364,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20156
20364
  return _this2.handleButtonClick(e, character, true);
20157
20365
  },
20158
20366
  onClick: function onClick(e) {
20159
- return e.preventDefault();
20367
+ e.preventDefault();
20368
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20160
20369
  }
20161
20370
  }, character);
20162
20371
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20174,7 +20383,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20174
20383
  return _this2.handleButtonClick(e, character, true);
20175
20384
  },
20176
20385
  onClick: function onClick(e) {
20177
- return e.preventDefault();
20386
+ e.preventDefault();
20387
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20178
20388
  }
20179
20389
  }, character);
20180
20390
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20192,7 +20402,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20192
20402
  return _this2.handleButtonClick(e, character, true);
20193
20403
  },
20194
20404
  onClick: function onClick(e) {
20195
- return e.preventDefault();
20405
+ e.preventDefault();
20406
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20196
20407
  }
20197
20408
  }, character);
20198
20409
  }))), this.state.selectedTab === "More" && /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20212,7 +20423,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20212
20423
  return _this2.handleButtonClick(e, character, true);
20213
20424
  },
20214
20425
  onClick: function onClick(e) {
20215
- return e.preventDefault();
20426
+ e.preventDefault();
20427
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20216
20428
  }
20217
20429
  }, character);
20218
20430
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20230,7 +20442,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20230
20442
  return _this2.handleButtonClick(e, character, true);
20231
20443
  },
20232
20444
  onClick: function onClick(e) {
20233
- return e.preventDefault();
20445
+ e.preventDefault();
20446
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20234
20447
  }
20235
20448
  }, character);
20236
20449
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20248,7 +20461,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20248
20461
  return _this2.handleButtonClick(e, character, true);
20249
20462
  },
20250
20463
  onClick: function onClick(e) {
20251
- return e.preventDefault();
20464
+ e.preventDefault();
20465
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20252
20466
  }
20253
20467
  }, character);
20254
20468
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20266,7 +20480,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20266
20480
  return _this2.handleButtonClick(e, character, true);
20267
20481
  },
20268
20482
  onClick: function onClick(e) {
20269
- return e.preventDefault();
20483
+ e.preventDefault();
20484
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20270
20485
  }
20271
20486
  }, character);
20272
20487
  })))))));
@@ -20302,8 +20517,10 @@ var Toolbar = /*#__PURE__*/function (_React$Component) {
20302
20517
  });
20303
20518
  });
20304
20519
  _defineProperty(_this, "handleButtonClick", function (event, buttonName) {
20520
+ var _this$props$beforeAct, _this$props;
20305
20521
  event.preventDefault();
20306
20522
  event.stopPropagation();
20523
+ (_this$props$beforeAct = (_this$props = _this.props).beforeAction) === null || _this$props$beforeAct === void 0 || _this$props$beforeAct.call(_this$props);
20307
20524
  _this.suppliedClickHandler(event, buttonName);
20308
20525
  });
20309
20526
  _defineProperty(_this, "handleAccentButtonClick", function (event, oldCharacter, newCharacter) {
@@ -20402,87 +20619,89 @@ var Toolbar = /*#__PURE__*/function (_React$Component) {
20402
20619
  style: style,
20403
20620
  ref: function ref(r) {
20404
20621
  return _this2.toolbar = r;
20405
- }
20622
+ },
20623
+ onBlur: this.props.onBlur
20406
20624
  }, enableFontStyling && /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement("button", {
20625
+ type: "button",
20407
20626
  name: "Bold",
20627
+ "aria-label": "Bold",
20408
20628
  className: "Toolbar-button TB-narrow" + (this.props.activeButtons.bold ? " TB-selected" : ""),
20409
20629
  onClick: function onClick(e) {
20410
- return e.preventDefault();
20630
+ return _this2.handleButtonClick(e, "Bold");
20411
20631
  },
20412
20632
  onMouseDown: function onMouseDown(e) {
20413
20633
  return e.preventDefault();
20414
- },
20415
- onMouseUp: function onMouseUp(e) {
20416
- return _this2.handleButtonClick(e, "Bold");
20417
20634
  }
20418
20635
  }, /*#__PURE__*/React__default["default"].createElement("b", null, "B")), /*#__PURE__*/React__default["default"].createElement("button", {
20636
+ type: "button",
20419
20637
  name: "Italic",
20638
+ "aria-label": "Italic",
20420
20639
  className: "Toolbar-button TB-narrow" + (this.props.activeButtons.italic ? " TB-selected" : ""),
20421
20640
  onClick: function onClick(e) {
20422
- return e.preventDefault();
20641
+ return _this2.handleButtonClick(e, "Italic");
20423
20642
  },
20424
20643
  onMouseDown: function onMouseDown(e) {
20425
20644
  return e.preventDefault();
20426
- },
20427
- onMouseUp: function onMouseUp(e) {
20428
- return _this2.handleButtonClick(e, "Italic");
20429
20645
  }
20430
20646
  }, /*#__PURE__*/React__default["default"].createElement("i", null, "I")), /*#__PURE__*/React__default["default"].createElement("button", {
20647
+ type: "button",
20431
20648
  name: "Underline",
20649
+ "aria-label": "Underline",
20432
20650
  className: "Toolbar-button TB-narrow" + (this.props.activeButtons.underline ? " TB-selected" : ""),
20433
20651
  onClick: function onClick(e) {
20434
- return e.preventDefault();
20652
+ return _this2.handleButtonClick(e, "Underline");
20435
20653
  },
20436
20654
  onMouseDown: function onMouseDown(e) {
20437
20655
  return e.preventDefault();
20438
- },
20439
- onMouseUp: function onMouseUp(e) {
20440
- return _this2.handleButtonClick(e, "Underline");
20441
20656
  }
20442
20657
  }, /*#__PURE__*/React__default["default"].createElement("u", null, "U")), /*#__PURE__*/React__default["default"].createElement("button", {
20658
+ type: "button",
20443
20659
  name: "Superscript",
20660
+ "aria-label": "Superscript",
20444
20661
  className: "Toolbar-button TB-narrow" + (this.props.activeButtons.superscript ? " TB-selected" : ""),
20445
20662
  onClick: function onClick(e) {
20446
- return e.preventDefault();
20663
+ return _this2.handleButtonClick(e, "Superscript");
20447
20664
  },
20448
20665
  onMouseDown: function onMouseDown(e) {
20449
20666
  return e.preventDefault();
20450
- },
20451
- onMouseUp: function onMouseUp(e) {
20452
- return _this2.handleButtonClick(e, "Superscript");
20453
20667
  }
20454
20668
  }, "a", /*#__PURE__*/React__default["default"].createElement("sup", null, "x")), /*#__PURE__*/React__default["default"].createElement("button", {
20669
+ type: "button",
20455
20670
  name: "Subscript",
20671
+ "aria-label": "Subscript",
20456
20672
  className: "Toolbar-button TB-narrow" + (this.props.activeButtons.subscript ? " TB-selected" : ""),
20457
20673
  onClick: function onClick(e) {
20458
- return e.preventDefault();
20674
+ return _this2.handleButtonClick(e, "Subscript");
20459
20675
  },
20460
20676
  onMouseDown: function onMouseDown(e) {
20461
20677
  return e.preventDefault();
20462
- },
20463
- onMouseUp: function onMouseUp(e) {
20464
- return _this2.handleButtonClick(e, "Subscript");
20465
20678
  }
20466
20679
  }, "a", /*#__PURE__*/React__default["default"].createElement("sub", null, "x"))), /*#__PURE__*/React__default["default"].createElement("button", {
20680
+ type: "button",
20467
20681
  name: "Accents",
20682
+ "aria-label": "Accents",
20683
+ "aria-expanded": this.state.showAccents,
20468
20684
  className: "Toolbar-button TB-narrow" + (this.state.showAccents ? " TB-selected" : ""),
20469
20685
  onMouseDown: function onMouseDown(e) {
20470
20686
  return e.preventDefault();
20471
20687
  },
20472
20688
  onClick: function onClick(e) {
20473
- return e.preventDefault();
20474
- },
20475
- onMouseUp: function onMouseUp(e) {
20476
- return _this2.showOrHideAccentBar(e);
20689
+ var _this2$props$beforeAc, _this2$props;
20690
+ e.preventDefault();
20691
+ e.stopPropagation();
20692
+ (_this2$props$beforeAc = (_this2$props = _this2.props).beforeAction) === null || _this2$props$beforeAc === void 0 || _this2$props$beforeAc.call(_this2$props);
20693
+ _this2.showOrHideAccentBar(e);
20477
20694
  }
20478
20695
  }, "\xE1"), enableMath && /*#__PURE__*/React__default["default"].createElement("button", {
20696
+ type: "button",
20479
20697
  name: "Equation",
20698
+ "aria-label": "Insert equation",
20480
20699
  className: "Toolbar-button TB-wide",
20481
20700
  onClick: function onClick(e) {
20482
- return e.preventDefault();
20701
+ return _this2.handleButtonClick(e, "Equation");
20483
20702
  },
20484
20703
  onMouseDown: function onMouseDown(e) {
20485
- return _this2.handleButtonClick(e, "Equation");
20704
+ return e.preventDefault();
20486
20705
  }
20487
20706
  }, /*#__PURE__*/React__default["default"].createElement("div", {
20488
20707
  dangerouslySetInnerHTML: {
@@ -20774,6 +20993,8 @@ function getMark() {
20774
20993
  return MARK$1;
20775
20994
  }
20776
20995
  function isKatexNode(node) {
20996
+ // Callers probe siblings and parents that may not exist (the first node has no previousSibling).
20997
+ if (node === null || node === undefined) return false;
20777
20998
  return node.className === "katex" || node.className === "katex-error";
20778
20999
  }
20779
21000
  function isTextNode(node) {
@@ -21479,7 +21700,6 @@ function getRangeParams(editableDiv) {
21479
21700
  }
21480
21701
  var selection = window.getSelection();
21481
21702
  if (selection.rangeCount === 0) {
21482
- console.error("Can't get range params, window.getSelection did not return any ranges");
21483
21703
  return null;
21484
21704
  }
21485
21705
  var range = selection.getRangeAt(0);
@@ -21972,6 +22192,229 @@ function plainTextOffsetToGlobalOffset(editableDiv, plainTextOffset) {
21972
22192
  */
21973
22193
  var SELECTION_MARK = MARK$1;
21974
22194
 
22195
+ /**
22196
+ * Capture browser editing intent in the logical text coordinates used by MathRichInput.
22197
+ *
22198
+ * The component deliberately reports a measured replacement range instead of asking a host to diff the
22199
+ * old and new strings. A host can therefore retain two equal names independently and reject a stale
22200
+ * event before it changes protected content.
22201
+ */
22202
+
22203
+ var ZERO_WIDTH_SPACE = "\u200B";
22204
+ var BLOCK_TAGS = new Set(["p", "div", "section", "article", "blockquote", "pre", "li", "ul", "ol", "h1", "h2", "h3", "h4", "h5", "h6", "table", "thead", "tbody", "tfoot", "tr", "td", "th", "hr"]);
22205
+ var INERT_TAGS = new Set(["script", "style", "template", "head", "title"]);
22206
+
22207
+ /** Return whether a node is the rendered KaTeX island that cannot be edited character by character. */
22208
+ function isMathNode(node) {
22209
+ var _node$classList, _node$classList2;
22210
+ return node && node.nodeType === 1 && (((_node$classList = node.classList) === null || _node$classList === void 0 ? void 0 : _node$classList.contains("katex")) || ((_node$classList2 = node.classList) === null || _node$classList2 === void 0 ? void 0 : _node$classList2.contains("katex-error")));
22211
+ }
22212
+
22213
+ /** Return whether a tag contributes a logical line boundary to the source text. */
22214
+ function isBlock(node) {
22215
+ if (!node || node.nodeType !== 1) return false;
22216
+ return BLOCK_TAGS.has(node.tagName.toLowerCase());
22217
+ }
22218
+ function isInert(node) {
22219
+ if (!node || node.nodeType !== 1) return false;
22220
+ return INERT_TAGS.has(node.tagName.toLowerCase());
22221
+ }
22222
+ function trimZeroWidth(value) {
22223
+ return value.split(ZERO_WIDTH_SPACE).join("");
22224
+ }
22225
+
22226
+ /** Build the text coordinate string without reading attributes or rendered math markup. */
22227
+ function editableText(root) {
22228
+ if (!root) return "";
22229
+ var pieces = [];
22230
+ var pendingBoundary = false;
22231
+ var walk = function walk(node) {
22232
+ if (isInert(node)) return;
22233
+ if (isMathNode(node)) {
22234
+ var _node$querySelector;
22235
+ if (pendingBoundary && pieces.length && pieces[pieces.length - 1] !== "\n") pieces.push("\n");
22236
+ var latex = ((_node$querySelector = node.querySelector) === null || _node$querySelector === void 0 || (_node$querySelector = _node$querySelector.call(node, 'annotation[encoding="application/x-tex"]')) === null || _node$querySelector === void 0 ? void 0 : _node$querySelector.textContent) || "";
22237
+ pieces.push(latex);
22238
+ pendingBoundary = false;
22239
+ return;
22240
+ }
22241
+ if (node.nodeType === 3) {
22242
+ var text = trimZeroWidth(node.nodeValue || "");
22243
+ if (text && pendingBoundary && pieces.length && pieces[pieces.length - 1] !== "\n") pieces.push("\n");
22244
+ if (text) pieces.push(text);
22245
+ if (text) pendingBoundary = false;
22246
+ return;
22247
+ }
22248
+ if (node.nodeType !== 1 && node.nodeType !== 11) return;
22249
+ if (node.nodeType === 1 && node.tagName.toLowerCase() === "br") {
22250
+ pieces.push("\n");
22251
+ pendingBoundary = false;
22252
+ return;
22253
+ }
22254
+ var block = isBlock(node);
22255
+ if (block && pieces.length) pendingBoundary = true;
22256
+ var _iterator = _createForOfIteratorHelper(node.childNodes || []),
22257
+ _step;
22258
+ try {
22259
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
22260
+ var child = _step.value;
22261
+ walk(child);
22262
+ }
22263
+ } catch (err) {
22264
+ _iterator.e(err);
22265
+ } finally {
22266
+ _iterator.f();
22267
+ }
22268
+ if (block) pendingBoundary = true;
22269
+ };
22270
+ var _iterator2 = _createForOfIteratorHelper(root.childNodes || []),
22271
+ _step2;
22272
+ try {
22273
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
22274
+ var child = _step2.value;
22275
+ walk(child);
22276
+ }
22277
+ } catch (err) {
22278
+ _iterator2.e(err);
22279
+ } finally {
22280
+ _iterator2.f();
22281
+ }
22282
+ return pieces.join("");
22283
+ }
22284
+ function textBeforeEndpoint(root, container, offset) {
22285
+ if (!root || !container || !root.contains(container)) return null;
22286
+ var range = document.createRange();
22287
+ range.selectNodeContents(root);
22288
+ try {
22289
+ range.setEnd(container, offset);
22290
+ } catch (_error) {
22291
+ return null;
22292
+ }
22293
+ var clone = document.createElement("span");
22294
+ clone.appendChild(range.cloneContents());
22295
+ return editableText(clone).length;
22296
+ }
22297
+
22298
+ /** Read a collapsed or selected DOM range in logical UTF-16 offsets. */
22299
+ function selectionOffsets(root) {
22300
+ var _window$getSelection, _window;
22301
+ var selection = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : (_window$getSelection = (_window = window).getSelection) === null || _window$getSelection === void 0 ? void 0 : _window$getSelection.call(_window);
22302
+ if (!root || !selection || selection.rangeCount === 0) return null;
22303
+ var range = selection.getRangeAt(0);
22304
+ var from = textBeforeEndpoint(root, range.startContainer, range.startOffset);
22305
+ var to = textBeforeEndpoint(root, range.endContainer, range.endOffset);
22306
+ if (from === null || to === null) return null;
22307
+ return from <= to ? {
22308
+ from: from,
22309
+ to: to
22310
+ } : {
22311
+ from: to,
22312
+ to: from
22313
+ };
22314
+ }
22315
+ function expandSurrogateBoundary(text, from, to) {
22316
+ var before = text.charCodeAt(from - 1);
22317
+ var after = text.charCodeAt(from);
22318
+ if (before >= 0xd800 && before <= 0xdbff && after >= 0xdc00 && after <= 0xdfff) {
22319
+ return {
22320
+ from: from - 1,
22321
+ to: Math.max(to, from + 1)
22322
+ };
22323
+ }
22324
+ var at = text.charCodeAt(to - 1);
22325
+ var next = text.charCodeAt(to);
22326
+ if (at >= 0xd800 && at <= 0xdbff && next >= 0xdc00 && next <= 0xdfff) {
22327
+ return {
22328
+ from: Math.min(from, to - 1),
22329
+ to: to + 1
22330
+ };
22331
+ }
22332
+ return {
22333
+ from: from,
22334
+ to: to
22335
+ };
22336
+ }
22337
+ function textFromTransfer(dataTransfer) {
22338
+ var _dataTransfer$getData, _dataTransfer$getData2;
22339
+ if (!dataTransfer) return "";
22340
+ var plain = ((_dataTransfer$getData = dataTransfer.getData) === null || _dataTransfer$getData === void 0 ? void 0 : _dataTransfer$getData.call(dataTransfer, "text/plain")) || "";
22341
+ if (plain) return plain;
22342
+ var html = ((_dataTransfer$getData2 = dataTransfer.getData) === null || _dataTransfer$getData2 === void 0 ? void 0 : _dataTransfer$getData2.call(dataTransfer, "text/html")) || "";
22343
+ if (!html || typeof document === "undefined") return "";
22344
+ var probe = document.createElement("div");
22345
+ probe.innerHTML = html;
22346
+ return editableText(probe);
22347
+ }
22348
+
22349
+ /**
22350
+ * Convert a browser `beforeinput` event into an explicit replacement.
22351
+ *
22352
+ * A null return means the browser supplied no safe range or the operation is outside the supported text
22353
+ * edits. The caller still emits the intent, without an edit, so a protected host can refuse it loudly.
22354
+ */
22355
+ function transactionFromBeforeInput(root, event) {
22356
+ var _event$data;
22357
+ var compositionStart = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
22358
+ var before = editableText(root);
22359
+ var selection = selectionOffsets(root);
22360
+ if (!selection) return {
22361
+ before: before,
22362
+ intent: intentForInputType(event === null || event === void 0 ? void 0 : event.inputType),
22363
+ edit: null
22364
+ };
22365
+ var inputType = (event === null || event === void 0 ? void 0 : event.inputType) || "insertText";
22366
+ var intent = intentForInputType(inputType);
22367
+ var from = selection.from,
22368
+ to = selection.to;
22369
+ var insert = (_event$data = event === null || event === void 0 ? void 0 : event.data) !== null && _event$data !== void 0 ? _event$data : "";
22370
+ if (inputType === "insertFromPaste" || inputType === "insertFromDrop") {
22371
+ insert = textFromTransfer(event === null || event === void 0 ? void 0 : event.dataTransfer);
22372
+ } else if (inputType === "insertLineBreak" || inputType === "insertParagraph") {
22373
+ insert = "\n";
22374
+ } else if (inputType === "deleteContentBackward" && from === to) {
22375
+ var _expandSurrogateBound = expandSurrogateBoundary(before, Math.max(0, from - 1), from);
22376
+ from = _expandSurrogateBound.from;
22377
+ to = _expandSurrogateBound.to;
22378
+ } else if (inputType === "deleteContentForward" && from === to) {
22379
+ var _expandSurrogateBound2 = expandSurrogateBoundary(before, from, Math.min(before.length, to + 1));
22380
+ from = _expandSurrogateBound2.from;
22381
+ to = _expandSurrogateBound2.to;
22382
+ } else if (inputType.startsWith("delete") && from === to) {
22383
+ return {
22384
+ before: before,
22385
+ intent: intent,
22386
+ edit: null
22387
+ };
22388
+ }
22389
+ var base = compositionStart && intent === "composition" ? compositionStart : {
22390
+ before: before,
22391
+ from: from,
22392
+ to: to
22393
+ };
22394
+ return {
22395
+ before: base.before,
22396
+ intent: intent,
22397
+ edit: {
22398
+ before: base.before,
22399
+ from: base.from,
22400
+ to: base.to,
22401
+ insert: insert
22402
+ }
22403
+ };
22404
+ }
22405
+
22406
+ /** Give hosts a stable vocabulary while preserving the browser's native inputType for diagnostics. */
22407
+ function intentForInputType(inputType) {
22408
+ if (!inputType) return "input";
22409
+ if (inputType.includes("Composition")) return "composition";
22410
+ if (inputType.includes("Paste")) return "paste";
22411
+ if (inputType.includes("Drop")) return "drop";
22412
+ if (inputType.includes("Cut")) return "cut";
22413
+ if (inputType === "historyUndo") return "undo";
22414
+ if (inputType === "historyRedo") return "redo";
22415
+ return "input";
22416
+ }
22417
+
21975
22418
  var MIME_TYPE_PLAIN_TEXT = "text/plain";
21976
22419
  var MIME_TYPE_HTML = "text/html";
21977
22420
  var MIME_TYPE_TEX = "application/x-tex";
@@ -22178,23 +22621,34 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
22178
22621
  var options = _this2.props.options || DEFAULT_OPTIONS;
22179
22622
  if (isExpertMode === null) isExpertMode = options.isExpertMode;
22180
22623
  if (selectedTab === null) selectedTab = options.selectedTab;
22181
-
22182
- // Add this to the history
22183
- _this2.undoHistory.push({
22624
+ var transaction = applyOptions.transaction || null;
22625
+ var historyState = {
22184
22626
  value: value,
22185
22627
  mimeType: mimeType,
22186
- rangeParams: rangeParams
22187
- });
22188
- if (_this2.undoHistory.length > MAX_HISTORY_LENGTH) _this2.undoHistory.shift();
22628
+ rangeParams: rangeParams,
22629
+ transaction: transaction
22630
+ };
22631
+ if (applyOptions.recordHistory !== false) {
22632
+ // Add this to the history. The transaction is retained beside the rendered value so a host can
22633
+ // restore annotation snapshots on undo/redo instead of interpreting an inverse spelling anew.
22634
+ _this2.undoHistory.push(historyState);
22635
+ if (_this2.undoHistory.length > MAX_HISTORY_LENGTH) _this2.undoHistory.shift();
22636
+ }
22189
22637
 
22190
22638
  // Debug what's being passed to parent
22191
22639
  // console.log("APPLY CHANGES:", { value, mimeType });
22192
22640
 
22193
22641
  // Call the parent handler to apply the changes
22194
- _this2.onChangeCallback({
22642
+ _this2.onChangeCallback(_objectSpread2(_objectSpread2(_objectSpread2({
22195
22643
  value: value,
22196
22644
  mimeType: mimeType
22197
- }, {
22645
+ }, transaction !== null && transaction !== void 0 && transaction.edit ? {
22646
+ edit: transaction.edit
22647
+ } : {}), transaction !== null && transaction !== void 0 && transaction.intent ? {
22648
+ intent: transaction.intent
22649
+ } : {}), transaction !== null && transaction !== void 0 && transaction.history ? {
22650
+ history: transaction.history
22651
+ } : {}), {
22198
22652
  isExpertMode: isExpertMode,
22199
22653
  selectedTab: selectedTab
22200
22654
  });
@@ -22219,7 +22673,8 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
22219
22673
  if (_this2.initialHistoryState == null) _this2.initialHistoryState = {
22220
22674
  value: _this2.props.value,
22221
22675
  mimeType: _this2.props.mimeType,
22222
- rangeParams: _this2._getRangeParams()
22676
+ rangeParams: _this2._getRangeParams(),
22677
+ transaction: null
22223
22678
  };
22224
22679
  });
22225
22680
  _defineProperty(_this2, "resetHistoryRedo", function () {
@@ -22238,22 +22693,59 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
22238
22693
  return true;
22239
22694
  });
22240
22695
  _defineProperty(_this2, "undo", function () {
22241
- var currentState = _this2.undoHistory.pop();
22242
- var previousState = _this2.undoHistory.pop();
22243
- if (previousState === null || previousState === undefined) {
22244
- previousState = _this2.initialHistoryState;
22245
- }
22246
- if (_this2.redoHistory.length > 0) {
22247
- var redoLast = _this2.redoHistory[_this2.redoHistory.length - 1];
22248
- if (!_this2.deepEqual(redoLast, currentState)) _this2.redoHistory.push(currentState);
22249
- } else _this2.redoHistory.push(currentState);
22250
- _this2.applyChangesToComponent(previousState.value, previousState.mimeType, previousState.rangeParams);
22696
+ var currentState = _this2.undoHistory[_this2.undoHistory.length - 1];
22697
+ if (!currentState) return;
22698
+ _this2.undoHistory.pop();
22699
+ var previousState = _this2.undoHistory[_this2.undoHistory.length - 1] || _this2.initialHistoryState;
22700
+ if (!previousState) return;
22701
+ _this2.redoHistory.push(currentState);
22702
+ var transaction = _this2.historyTransaction(currentState, "undo");
22703
+ _this2.applyChangesToComponent(previousState.value, previousState.mimeType, previousState.rangeParams, null, null, {
22704
+ transaction: transaction,
22705
+ recordHistory: false
22706
+ });
22251
22707
  });
22252
22708
  _defineProperty(_this2, "redo", function () {
22253
22709
  var redoState = _this2.redoHistory.pop();
22254
- if (redoState !== null & redoState !== undefined) {
22255
- _this2.applyChangesToComponent(redoState.value, redoState.mimeType, redoState.rangeParams);
22710
+ if (redoState !== null && redoState !== undefined) {
22711
+ _this2.applyChangesToComponent(redoState.value, redoState.mimeType, redoState.rangeParams, null, null, {
22712
+ transaction: _this2.historyTransaction(redoState, "redo"),
22713
+ recordHistory: false
22714
+ });
22715
+ _this2.undoHistory.push(redoState);
22716
+ }
22717
+ });
22718
+ /** Build an explicit forward/inverse transaction for one history entry. */
22719
+ _defineProperty(_this2, "historyTransaction", function (state, direction) {
22720
+ var _original$history;
22721
+ var original = state === null || state === void 0 ? void 0 : state.transaction;
22722
+ if (!(original !== null && original !== void 0 && (_original$history = original.history) !== null && _original$history !== void 0 && _original$history.id) || !original.edit) {
22723
+ return {
22724
+ intent: direction,
22725
+ history: original !== null && original !== void 0 && original.history ? {
22726
+ id: original.history.id,
22727
+ direction: direction
22728
+ } : undefined
22729
+ };
22256
22730
  }
22731
+ var edit = original.edit;
22732
+ var after = edit.before.slice(0, edit.from) + edit.insert + edit.before.slice(edit.to);
22733
+ var inverse = _objectSpread2({
22734
+ before: after,
22735
+ from: edit.from,
22736
+ to: edit.from + edit.insert.length,
22737
+ insert: edit.before.slice(edit.from, edit.to)
22738
+ }, edit.boundary ? {
22739
+ boundary: edit.boundary
22740
+ } : {});
22741
+ return {
22742
+ intent: direction,
22743
+ history: {
22744
+ id: original.history.id,
22745
+ direction: direction
22746
+ },
22747
+ edit: direction === "undo" ? inverse : edit
22748
+ };
22257
22749
  });
22258
22750
  _defineProperty(_this2, "_setRangeParams", function (params) {
22259
22751
  try {
@@ -22295,6 +22787,7 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
22295
22787
  restore(50);
22296
22788
  });
22297
22789
  _defineProperty(_this2, "handleKeyDownBackspace", function (e) {
22790
+ var beforeText = editableText(_this2.editableDiv);
22298
22791
  var rangeParams = _this2._getRangeParams();
22299
22792
  var start_node_index = rangeParams.startNodeIndex;
22300
22793
  var end_node_index = rangeParams.endNodeIndex;
@@ -22331,7 +22824,21 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
22331
22824
  startGlobalOffset: 0,
22332
22825
  endGlobalOffset: 0
22333
22826
  };
22334
- _this2.applyChangesToComponent("", _this2.getMimeType(false, false), _new_rangeParams, _this2.props.useExpertMode, _this2.props.selectedTab);
22827
+ _this2.applyChangesToComponent("", _this2.getMimeType(false, false), _new_rangeParams, _this2.props.useExpertMode, _this2.props.selectedTab, {
22828
+ transaction: {
22829
+ edit: {
22830
+ before: beforeText,
22831
+ from: 0,
22832
+ to: beforeText.length,
22833
+ insert: ""
22834
+ },
22835
+ intent: "cut",
22836
+ history: {
22837
+ id: "mri-".concat(++_this2.historySequence),
22838
+ direction: "forward"
22839
+ }
22840
+ }
22841
+ });
22335
22842
  e.preventDefault();
22336
22843
  return;
22337
22844
  }
@@ -23188,6 +23695,11 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23188
23695
  try {
23189
23696
  // console.log("handleInput")
23190
23697
  if (_this2.isComposing) return;
23698
+ // execCommand emits a synchronous input event; styleText records the
23699
+ // complete format transaction once after the command returns.
23700
+ if (_this2.formatCommandActive) return;
23701
+ var transaction = _this2.pendingTransaction;
23702
+ _this2.pendingTransaction = null;
23191
23703
  debugMathRichInput("handleInput:start", {
23192
23704
  inputType: event && event.nativeEvent ? event.nativeEvent.inputType : null,
23193
23705
  data: event && event.nativeEvent ? event.nativeEvent.data : null,
@@ -23224,7 +23736,8 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23224
23736
  _raw_text = removeEncodingIfPlainText(_raw_text, _mimeType, _this2.enableHtml());
23225
23737
  _raw_text = _this2.stripEmptyInlineFormattingTags(_raw_text);
23226
23738
  _this2.applyChangesToComponent(_raw_text, _mimeType, defaultParams, _this2.props.useExpertMode, _this2.props.selectedTab, {
23227
- skipControlledRender: true
23739
+ skipControlledRender: true,
23740
+ transaction: transaction
23228
23741
  });
23229
23742
  return;
23230
23743
  } else if (params.startNodeIndex === null || params.startNodeIndex === undefined) {
@@ -23259,7 +23772,8 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23259
23772
  };
23260
23773
  _this2.setOldRangeParams(validParams);
23261
23774
  _this2.applyChangesToComponent(raw_text, mimeType, validParams, _this2.props.useExpertMode, _this2.props.selectedTab, {
23262
- skipControlledRender: true
23775
+ skipControlledRender: true,
23776
+ transaction: transaction
23263
23777
  });
23264
23778
  debugMathRichInput("handleInput:after-apply", {
23265
23779
  raw_text: raw_text,
@@ -23356,6 +23870,7 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23356
23870
  });
23357
23871
  _defineProperty(_this2, "handlePaste", function (event) {
23358
23872
  try {
23873
+ _this2.pendingTransaction = null;
23359
23874
  var clipboardData = event.clipboardData || window.clipboardData;
23360
23875
  if (!clipboardData) {
23361
23876
  return;
@@ -23380,6 +23895,8 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23380
23895
  console.warn("Could not get range params for paste");
23381
23896
  return;
23382
23897
  }
23898
+ var beforePaste = editableText(_this2.editableDiv);
23899
+ var pasteSelection = _this2.captureSelectionOffsets();
23383
23900
 
23384
23901
  // Store old range params for cursor positioning (like equation editor)
23385
23902
  _this2.setOldRangeParams(rangeParams);
@@ -23605,7 +24122,34 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23605
24122
  var cleanedRawText = removeEncodingIfPlainText(newRawText, actualMimeType, _this2.enableHtml());
23606
24123
 
23607
24124
  // Apply the change using the component's standard flow
23608
- _this2.applyChangesToComponent(cleanedRawText, actualMimeType, newRangeParams, _this2.props.useExpertMode, _this2.props.selectedTab);
24125
+ var insertionText = function () {
24126
+ if (!/<[^>]+>/.test(finalText)) return finalText;
24127
+ var probe = document.createElement("div");
24128
+ probe.innerHTML = finalText;
24129
+ return editableText(probe).replace(/\ufffc/g, "");
24130
+ }();
24131
+ var transaction = pasteSelection ? {
24132
+ edit: {
24133
+ before: beforePaste,
24134
+ from: pasteSelection.from,
24135
+ to: pasteSelection.to,
24136
+ insert: insertionText
24137
+ },
24138
+ intent: "paste",
24139
+ history: {
24140
+ id: "mri-".concat(++_this2.historySequence),
24141
+ direction: "forward"
24142
+ }
24143
+ } : {
24144
+ intent: "paste",
24145
+ history: {
24146
+ id: "mri-".concat(++_this2.historySequence),
24147
+ direction: "forward"
24148
+ }
24149
+ };
24150
+ _this2.applyChangesToComponent(cleanedRawText, actualMimeType, newRangeParams, _this2.props.useExpertMode, _this2.props.selectedTab, {
24151
+ transaction: transaction
24152
+ });
23609
24153
 
23610
24154
  // A paste cannot rely on being re-rendered. It removed the selected content from the DOM itself,
23611
24155
  // and if the host already holds the value being applied — the same words pasted back into the
@@ -23634,7 +24178,9 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23634
24178
  var moveCursorOnInsert = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
23635
24179
  var node = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
23636
24180
  try {
24181
+ var _node;
23637
24182
  var rangeParams = _this2._getRangeParams();
24183
+ var hasNode = node !== null && node !== undefined;
23638
24184
  debugMathRichInput("showEquationEditor:start", {
23639
24185
  moveCursorOnInsert: moveCursorOnInsert,
23640
24186
  nodeProvided: Boolean(node),
@@ -23643,8 +24189,27 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23643
24189
  innerHTML: _this2.editableDiv ? _this2.editableDiv.innerHTML : null,
23644
24190
  selection: getSelectionSnapshot(_this2.editableDiv)
23645
24191
  });
23646
- if (rangeParams.startNodeIndex < 0) {
23647
- console.error("Could not find position of cursor in node list");
24192
+ // A node handed in by a click is enough to open the editor, even when the selection resolves to
24193
+ // nothing (-1) or is missing entirely: the node carries the latex and where it sits.
24194
+ if (!hasNode && (rangeParams === null || rangeParams.startNodeIndex < 0) && !_this2.isVisuallyEmpty()) {
24195
+ // No usable caret in a field that has content. The empty-field fallback below must never run
24196
+ // here: its marks stand for the WHOLE value, so Insert would replace the answer with the formula.
24197
+ // Put the caret at the end of the field and resolve it again; if that still fails, do not open.
24198
+ var endOfField = document.createRange();
24199
+ endOfField.selectNodeContents(_this2.editableDiv);
24200
+ endOfField.collapse(false);
24201
+ _this2.setCaretToRange(endOfField);
24202
+ rangeParams = _this2._getRangeParams();
24203
+ if (rangeParams === null || rangeParams.startNodeIndex < 0) {
24204
+ console.error("Could not find position of cursor in node list");
24205
+ return;
24206
+ }
24207
+ }
24208
+ if (!hasNode && (rangeParams === null || rangeParams.startNodeIndex < 0)) {
24209
+ // An empty field: there is nothing to keep, so the equation becomes its whole value.
24210
+ debugMathRichInput("showEquationEditor:empty-field", {
24211
+ rangeParams: rangeParams
24212
+ });
23648
24213
  _this2.setOldRangeParams({
23649
24214
  startNodeIndex: 2,
23650
24215
  startOffset: 1,
@@ -23652,6 +24217,9 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23652
24217
  endOffset: 1
23653
24218
  });
23654
24219
  _this2.markedRawText = MARK + " " + MARK;
24220
+ // A new equation: never the formula an earlier opening was editing.
24221
+ _this2.editingExistingEquation = false;
24222
+ _this2.editingNode = null;
23655
24223
  _this2.moveCursorOnInsert = moveCursorOnInsert;
23656
24224
  _this2.setState({
23657
24225
  showEquationEditor: true,
@@ -23661,7 +24229,14 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23661
24229
  }
23662
24230
  var latex = "";
23663
24231
  var offset = 0;
23664
- if (node === null) node = _this2._findNodeWithIndex(rangeParams.startNodeIndex);
24232
+ if (!hasNode) node = _this2._findNodeWithIndex(rangeParams.startNodeIndex);
24233
+
24234
+ // The toolbar preserves a caret AFTER the formula, in the renderer's zero-width text node.
24235
+ // Resolve that boundary to the preceding formula so f(x) edits it instead of opening empty.
24236
+ // Ordinary text or a non-collapsed selection still starts a new equation.
24237
+ if (!hasNode && ((_node = node) === null || _node === void 0 ? void 0 : _node.nodeType) === Node.TEXT_NODE && rangeParams.startNodeIndex === rangeParams.endNodeIndex && rangeParams.startOffset === rangeParams.endOffset && node.textContent.slice(0, rangeParams.startOffset).replace(/\u200b/g, "") === "" && isKatexNode(node.previousSibling)) {
24238
+ node = node.previousSibling;
24239
+ }
23665
24240
 
23666
24241
  // console.log(rangeParams)
23667
24242
  // console.log(this.editableDiv.childNodes)
@@ -23681,7 +24256,7 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23681
24256
  _this2.editingNode = node;
23682
24257
  } else {
23683
24258
  latex = "";
23684
- offset = rangeParams.startOffset;
24259
+ offset = rangeParams ? rangeParams.startOffset : 0;
23685
24260
  _this2.editingExistingEquation = false;
23686
24261
  _this2.editingNode = null;
23687
24262
  }
@@ -23709,6 +24284,7 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23709
24284
  _this2.isMouseDownInEditable = false;
23710
24285
  }, 0);
23711
24286
  _this2.showEquationEditorOnMouseUp = false;
24287
+ _this2.equationEditorMouseDownKatexIndex = -1;
23712
24288
  var node = null;
23713
24289
  try {
23714
24290
  node = event.target;
@@ -23724,6 +24300,11 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23724
24300
  return;
23725
24301
  }
23726
24302
  _this2.showEquationEditorOnMouseUp = true;
24303
+ // The browser has no selection yet at mousedown and a click inside KaTeX may leave it inside the
24304
+ // rendered subtree or outside the field. The formula's POSITION is what survives until mouseup:
24305
+ // focusing the field redraws the editable and replaces every node with an identical one, so the
24306
+ // element pressed here is detached by the time the mouse is released.
24307
+ _this2.equationEditorMouseDownKatexIndex = _this2.katexNodesInField().indexOf(node);
23727
24308
  // Save current range parameters so that we can restore them later if we wish
23728
24309
  _this2.setOldRangeParams(_this2._getRangeParams());
23729
24310
 
@@ -23742,6 +24323,25 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23742
24323
 
23743
24324
  // Clear selection anchor on mouse click
23744
24325
  _this2.selectionAnchor = null;
24326
+ var selection = window.getSelection();
24327
+ var collapsedClick = selection === null || selection.rangeCount === 0 || selection.isCollapsed;
24328
+
24329
+ // A click on a formula opens the equation editor for THAT formula. The selection is not used to
24330
+ // find it: clicking KaTeX leaves the selection inside the rendered subtree (which the text model
24331
+ // reads as the position before the maths) or outside the field, and the editor then opened empty
24332
+ // and the caret came back at the start. The caret is put after the formula, which is one logical
24333
+ // character, so typing and Insert continue to its right.
24334
+ if (collapsedClick && _this2.showEquationEditorOnMouseUp) {
24335
+ // Focus BEFORE the node is looked up: focusing redraws the field, and a node resolved from the
24336
+ // old DOM would be detached by the time the range is built.
24337
+ _this2.editableDiv.focus();
24338
+ var mathNode = _this2.katexNodesInField()[_this2.equationEditorMouseDownKatexIndex] || null;
24339
+ if (mathNode !== null) {
24340
+ _this2.setCaretToRange(_this2.caretRangeAfterNode(mathNode));
24341
+ _this2.showEquationEditor(false, mathNode);
24342
+ return;
24343
+ }
24344
+ }
23745
24345
 
23746
24346
  // A click that lands with the selection outside this field — a toolbar button, another field —
23747
24347
  // gives no range at all. Dereferenced unguarded, this threw on every such click.
@@ -23754,11 +24354,6 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23754
24354
  console.warn("Start or end node was null");
23755
24355
  return;
23756
24356
  }
23757
- if (params.startNodeIndex === params.endNodeIndex && params.startOffset === params.endOffset && _this2.showEquationEditorOnMouseUp) {
23758
- _this2.editableDiv.focus();
23759
- _this2.showEquationEditor(false, startNode);
23760
- return;
23761
- }
23762
24357
 
23763
24358
  // If our range starts or ends inside a katex node, move it out
23764
24359
  var new_rangeParams = null;
@@ -23779,6 +24374,10 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23779
24374
  new_rangeParams.endOffset = SMALL_SPACE_LENGTH;
23780
24375
  }
23781
24376
  if (new_rangeParams !== null) {
24377
+ // These node positions replace a selection inside KaTeX. Its old global offsets still point
24378
+ // before the atomic formula, so they must not override the corrected positions after it.
24379
+ new_rangeParams.startGlobalOffset = null;
24380
+ new_rangeParams.endGlobalOffset = null;
23782
24381
  _this2._setRangeParams(new_rangeParams);
23783
24382
  event.preventDefault();
23784
24383
  return;
@@ -23812,6 +24411,27 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23812
24411
  innerHTML: _this2.editableDiv ? _this2.editableDiv.innerHTML : null,
23813
24412
  selection: getSelectionSnapshot(_this2.editableDiv)
23814
24413
  });
24414
+
24415
+ // Insert with nothing to write: a formula that was opened and not edited, or a new equation left
24416
+ // empty. Writing back would send the host a change event for a value it already holds, and hosts
24417
+ // show "unsaved changes" on any change event. Close as Escape does and put the caret back.
24418
+ if (_this2.editingExistingEquation && latex === _this2.state.equationEditorLatex || !_this2.editingExistingEquation && latex === "") {
24419
+ var unchangedRangeParams = _this2.getOldRangeParams();
24420
+ _this2.setState({
24421
+ showEquationEditor: false
24422
+ }, function () {
24423
+ try {
24424
+ _this2.editableDiv.focus();
24425
+ if (unchangedRangeParams !== null) {
24426
+ _this2._setRangeParams(unchangedRangeParams);
24427
+ _this2.restoreRangeAfterBrowserWork(unchangedRangeParams, "equation-unchanged");
24428
+ }
24429
+ } catch (error) {
24430
+ console.error(error);
24431
+ }
24432
+ });
24433
+ return;
24434
+ }
23815
24435
  var start_pos = _this2.markedRawText.indexOf(MARK);
23816
24436
  var end_pos = _this2.markedRawText.indexOf(MARK, start_pos + MARK.length);
23817
24437
  var oldRangeParams = _this2.getOldRangeParams();
@@ -23932,6 +24552,9 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23932
24552
  // }
23933
24553
  _defineProperty(_this2, "styleText", function (event, style) {
23934
24554
  try {
24555
+ _this2.initialiseHistory();
24556
+ _this2.resetHistoryRedo();
24557
+ _this2.formatCommandActive = true;
23935
24558
  style = style.toLowerCase();
23936
24559
  var command = _this2.getStyleCommand(style);
23937
24560
  var activeBefore = _this2.getActiveButtonsFromSelection();
@@ -23939,12 +24562,14 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23939
24562
  var isCollapsed = selection === null || selection.rangeCount === 0 || selection.getRangeAt(0).collapsed;
23940
24563
  var success = false;
23941
24564
  var insertedTypingStylePlaceholder = false;
24565
+ var publishedByWhitespaceMove = false;
23942
24566
 
23943
24567
  // This first part is a workaround whereby executing subscript or superscript
23944
24568
  // within an existing subscript or superscript makes the text even smaller instead
23945
24569
  // of toggling it off. Thus we manually toggle it off with some jigerry pokery!
23946
24570
  if (isCollapsed && activeBefore[style] && _this2.moveTrailingWhitespaceOutsideInlineStyle(style, command)) {
23947
24571
  success = true;
24572
+ publishedByWhitespaceMove = true;
23948
24573
  } else if (isCollapsed && !activeBefore[style] && selection && selection.rangeCount > 0) {
23949
24574
  success = _this2.insertInlineStylePlaceholder(style);
23950
24575
  insertedTypingStylePlaceholder = success;
@@ -23966,15 +24591,29 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23966
24591
  }
23967
24592
  }
23968
24593
  var rangeParams = _this2._getRangeParams();
23969
- var removedEmptyTags = !insertedTypingStylePlaceholder && _this2.cleanupEmptyInlineFormattingElements();
23970
- if (removedEmptyTags && rangeParams !== null && rangeParams !== undefined) {
24594
+ if (!insertedTypingStylePlaceholder) _this2.cleanupEmptyInlineFormattingElements();
24595
+ if (!publishedByWhitespaceMove && rangeParams !== null && rangeParams !== undefined) {
23971
24596
  _this2._setRangeParams(rangeParams);
23972
- _this2.applyCurrentEditableDomToComponent(rangeParams);
24597
+ _this2.applyCurrentEditableDomToComponent(rangeParams, {
24598
+ edit: {
24599
+ before: editableText(_this2.editableDiv),
24600
+ from: 0,
24601
+ to: 0,
24602
+ insert: ""
24603
+ },
24604
+ intent: "format",
24605
+ history: {
24606
+ id: "mri-".concat(++_this2.historySequence),
24607
+ direction: "forward"
24608
+ }
24609
+ });
23973
24610
  }
23974
24611
  _this2.setActiveButtonsIfChanged(_this2.getActiveButtonsFromSelection(rangeParams));
23975
24612
  return success;
23976
24613
  } catch (error) {
23977
24614
  console.error(error);
24615
+ } finally {
24616
+ _this2.formatCommandActive = false;
23978
24617
  }
23979
24618
  });
23980
24619
  _defineProperty(_this2, "handleToolbarButtonClick", function (event, buttonName) {
@@ -24014,6 +24653,7 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24014
24653
  _defineProperty(_this2, "insertRawText", function (new_text) {
24015
24654
  var selectInsertedText = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
24016
24655
  try {
24656
+ var beforeText = editableText(_this2.editableDiv);
24017
24657
  var rangeParams = _this2._getRangeParams();
24018
24658
  var node = _this2._findNodeWithIndex(rangeParams.endNodeIndex);
24019
24659
  var offset = rangeParams.endOffset;
@@ -24045,7 +24685,21 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24045
24685
  // console.log(new_rangeParams)
24046
24686
 
24047
24687
  new_raw_text = removeEncodingIfPlainText(new_raw_text, _this2.props.mimeType, _this2.enableHtml());
24048
- _this2.applyChangesToComponent(new_raw_text, _this2.props.mimeType, new_rangeParams, _this2.props.useExpertMode, _this2.props.selectedTab);
24688
+ _this2.applyChangesToComponent(new_raw_text, _this2.props.mimeType, new_rangeParams, _this2.props.useExpertMode, _this2.props.selectedTab, {
24689
+ transaction: {
24690
+ edit: {
24691
+ before: beforeText,
24692
+ from: rangeParams.startGlobalOffset,
24693
+ to: rangeParams.endGlobalOffset,
24694
+ insert: new_text
24695
+ },
24696
+ intent: "input",
24697
+ history: {
24698
+ id: "mri-".concat(++_this2.historySequence),
24699
+ direction: "forward"
24700
+ }
24701
+ }
24702
+ });
24049
24703
  } catch (error) {
24050
24704
  console.error(error);
24051
24705
  }
@@ -24173,6 +24827,7 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24173
24827
  });
24174
24828
  _defineProperty(_this2, "removeNewLine", function () {
24175
24829
  try {
24830
+ var beforeText = editableText(_this2.editableDiv);
24176
24831
  var rangeParams = _this2._getRangeParams();
24177
24832
  var node = _this2._findNodeWithIndex(rangeParams.endNodeIndex);
24178
24833
  var prevNode = _this2._findNodeWithIndex(rangeParams.endNodeIndex - 1);
@@ -24189,13 +24844,28 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24189
24844
  new_rangeParams.srartGlobalOffset--;
24190
24845
  new_rangeParams.endGlobalOffset--;
24191
24846
  new_raw_text = removeEncodingIfPlainText(new_raw_text, _this2.props.mimeType, _this2.enableHtml());
24192
- _this2.applyChangesToComponent(new_raw_text, _this2.props.mimeType, new_rangeParams, _this2.props.useExpertMode, _this2.props.selectedTab);
24847
+ _this2.applyChangesToComponent(new_raw_text, _this2.props.mimeType, new_rangeParams, _this2.props.useExpertMode, _this2.props.selectedTab, {
24848
+ transaction: {
24849
+ edit: {
24850
+ before: beforeText,
24851
+ from: Math.max(0, rangeParams.endGlobalOffset - 1),
24852
+ to: rangeParams.endGlobalOffset,
24853
+ insert: ""
24854
+ },
24855
+ intent: "input",
24856
+ history: {
24857
+ id: "mri-".concat(++_this2.historySequence),
24858
+ direction: "forward"
24859
+ }
24860
+ }
24861
+ });
24193
24862
  } catch (error) {
24194
24863
  console.error(error);
24195
24864
  }
24196
24865
  });
24197
24866
  _defineProperty(_this2, "insertNewLine", function () {
24198
24867
  try {
24868
+ var beforeText = editableText(_this2.editableDiv);
24199
24869
  var rangeParams = _this2._getRangeParams();
24200
24870
  var node = _this2._findNodeWithIndex(rangeParams.endNodeIndex);
24201
24871
  var offset = rangeParams.endOffset;
@@ -24231,13 +24901,28 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24231
24901
  // console.log("New range params after line insert")
24232
24902
  // console.log(new_rangeParams)
24233
24903
 
24234
- _this2.applyChangesToComponent(new_raw_text, _this2.props.mimeType, new_rangeParams, _this2.props.useExpertMode, _this2.props.selectedTab);
24904
+ _this2.applyChangesToComponent(new_raw_text, _this2.props.mimeType, new_rangeParams, _this2.props.useExpertMode, _this2.props.selectedTab, {
24905
+ transaction: {
24906
+ edit: {
24907
+ before: beforeText,
24908
+ from: rangeParams.startGlobalOffset,
24909
+ to: rangeParams.endGlobalOffset,
24910
+ insert: "\n"
24911
+ },
24912
+ intent: "input",
24913
+ history: {
24914
+ id: "mri-".concat(++_this2.historySequence),
24915
+ direction: "forward"
24916
+ }
24917
+ }
24918
+ });
24235
24919
  } catch (error) {
24236
24920
  console.error(error);
24237
24921
  }
24238
24922
  });
24239
24923
  _defineProperty(_this2, "insertAccentLetter", function (event, old_char, new_char) {
24240
24924
  try {
24925
+ var beforeText = editableText(_this2.editableDiv);
24241
24926
  var rangeParams = _this2._getRangeParams();
24242
24927
  var node = _this2._findNodeWithIndex(rangeParams.startNodeIndex);
24243
24928
  var offset = rangeParams.startOffset;
@@ -24261,7 +24946,21 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24261
24946
  new_rangeParams = _objectSpread2({}, rangeParams);
24262
24947
  }
24263
24948
  new_raw_text = removeEncodingIfPlainText(new_raw_text, _this2.props.mimeType, _this2.enableHtml());
24264
- _this2.applyChangesToComponent(new_raw_text, _this2.props.mimeType, new_rangeParams, _this2.props.useExpertMode, _this2.props.selectedTab);
24949
+ _this2.applyChangesToComponent(new_raw_text, _this2.props.mimeType, new_rangeParams, _this2.props.useExpertMode, _this2.props.selectedTab, {
24950
+ transaction: {
24951
+ edit: {
24952
+ before: beforeText,
24953
+ from: old_char === null ? rangeParams.startGlobalOffset : Math.max(0, rangeParams.startGlobalOffset - 1),
24954
+ to: old_char === null ? rangeParams.startGlobalOffset : rangeParams.startGlobalOffset,
24955
+ insert: new_char
24956
+ },
24957
+ intent: "input",
24958
+ history: {
24959
+ id: "mri-".concat(++_this2.historySequence),
24960
+ direction: "forward"
24961
+ }
24962
+ }
24963
+ });
24265
24964
  } catch (error) {
24266
24965
  console.error(error);
24267
24966
  }
@@ -24306,12 +25005,21 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24306
25005
  currentRangeParams = null;
24307
25006
  }
24308
25007
  var savedRangeParams = _this2.getOldRangeParams();
24309
- var shouldRestoreSavedRange = !_this2.isMouseDownInEditable && _this2.props.value !== "" && _this2.rangeIsAtStart(currentRangeParams) && _this2.rangeHasNonZeroPosition(savedRangeParams);
25008
+ // A missing selection at focus time is not "somewhere else": the browser has not placed a caret
25009
+ // yet, which reads as the START of the field. Treating null as "leave it alone" skipped both the
25010
+ // restore below and the move to the end, so the caret stayed where the browser had left it — 0.
25011
+ var currentIsAtStart = currentRangeParams === null || _this2.rangeIsAtStart(currentRangeParams);
25012
+ var shouldRestoreSavedRange = !_this2.isMouseDownInEditable && _this2.props.value !== "" && currentIsAtStart && _this2.rangeHasNonZeroPosition(savedRangeParams);
25013
+ // Focus without a click inside the field (a host focusing it, Tab, a click on the row around it)
25014
+ // carries no position of its own. Editing an existing answer means continuing at its end, so the
25015
+ // caret goes there; a click inside the field keeps the position the browser gave it.
25016
+ var shouldMoveToEnd = !_this2.isMouseDownInEditable && _this2.props.value !== "" && currentIsAtStart && !_this2.rangeHasNonZeroPosition(savedRangeParams);
24310
25017
  debugMathRichInput("handleFocus:start", {
24311
25018
  propsValue: _this2.props.value,
24312
25019
  oldRangeParams: _this2.oldRangeParams,
24313
25020
  currentRangeParams: currentRangeParams,
24314
25021
  shouldRestoreSavedRange: shouldRestoreSavedRange,
25022
+ shouldMoveToEnd: shouldMoveToEnd,
24315
25023
  innerHTML: _this2.editableDiv ? _this2.editableDiv.innerHTML : null,
24316
25024
  selection: getSelectionSnapshot(_this2.editableDiv)
24317
25025
  });
@@ -24325,6 +25033,22 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24325
25033
  if (shouldRestoreSavedRange) {
24326
25034
  _this2._setRangeParams(savedRangeParams);
24327
25035
  _this2.restoreRangeAfterBrowserWork(savedRangeParams, "focus-restore");
25036
+ } else if (shouldMoveToEnd) {
25037
+ // One task later, not here: the browser places the caret as part of the focus default
25038
+ // action, and for a keyboard focus (Tab) that happens AFTER this callback — moving now was
25039
+ // simply overwritten. A task boundary is after both the pointer and the keyboard default.
25040
+ // The guard keeps a selection placed meanwhile (a host restoring its own range) untouched.
25041
+ window.setTimeout(function () {
25042
+ if (_this2.state.hasFocus !== true) return;
25043
+ var liveRangeParams = _this2._getRangeParams();
25044
+ if (liveRangeParams !== null && !_this2.rangeIsAtStart(liveRangeParams)) {
25045
+ return;
25046
+ }
25047
+ var endOfField = document.createRange();
25048
+ endOfField.selectNodeContents(_this2.editableDiv);
25049
+ endOfField.collapse(false);
25050
+ _this2.setCaretToRange(endOfField);
25051
+ }, 0);
24328
25052
  }
24329
25053
  debugMathRichInput("handleFocus:after-setState", {
24330
25054
  selection: getSelectionSnapshot(_this2.editableDiv)
@@ -24335,11 +25059,12 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24335
25059
  }
24336
25060
  });
24337
25061
  _defineProperty(_this2, "handleBlur", function (event) {
25062
+ var _this2$toolbar;
24338
25063
  var onBlur = _this2.props.onBlur;
24339
25064
  var currentRangeParams = null;
24340
25065
  try {
24341
25066
  currentRangeParams = _this2._getRangeParams();
24342
- if (_this2.rangeHasNonZeroPosition(currentRangeParams)) {
25067
+ if (currentRangeParams !== null) {
24343
25068
  _this2.setOldRangeParams(currentRangeParams);
24344
25069
  }
24345
25070
  } catch (error) {
@@ -24352,6 +25077,9 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24352
25077
  innerHTML: _this2.editableDiv ? _this2.editableDiv.innerHTML : null,
24353
25078
  selection: getSelectionSnapshot(_this2.editableDiv)
24354
25079
  });
25080
+ // Keyboard focus on this editor's tools is still inside the editor. Keep both
25081
+ // the toolbar and the captured range until focus leaves the whole control.
25082
+ if (event.relatedTarget === _this2.editableDiv || (_this2$toolbar = _this2.toolbar) !== null && _this2$toolbar !== void 0 && (_this2$toolbar = _this2$toolbar.toolbar) !== null && _this2$toolbar !== void 0 && _this2$toolbar.contains(event.relatedTarget)) return;
24355
25083
  if (onBlur != null) {
24356
25084
  onBlur();
24357
25085
  }
@@ -24369,10 +25097,66 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24369
25097
  }
24370
25098
  });
24371
25099
  _defineProperty(_this2, "handleCompositionStart", function (event) {
25100
+ var _offsets$from, _offsets$to;
25101
+ var offsets = _this2.captureSelectionOffsets();
25102
+ _this2.compositionStart = {
25103
+ before: editableText(_this2.editableDiv),
25104
+ from: (_offsets$from = offsets === null || offsets === void 0 ? void 0 : offsets.from) !== null && _offsets$from !== void 0 ? _offsets$from : 0,
25105
+ to: (_offsets$to = offsets === null || offsets === void 0 ? void 0 : offsets.to) !== null && _offsets$to !== void 0 ? _offsets$to : 0
25106
+ };
24372
25107
  _this2.isComposing = true;
24373
25108
  });
24374
25109
  _defineProperty(_this2, "handleCompositionEnd", function (event) {
24375
25110
  _this2.isComposing = false;
25111
+ _this2.compositionStart = null;
25112
+ });
25113
+ _defineProperty(_this2, "captureSelectionOffsets", function () {
25114
+ try {
25115
+ var selection = window.getSelection();
25116
+ if (!selection || selection.rangeCount === 0) return null;
25117
+ var range = selection.getRangeAt(0);
25118
+ var before = editableText(_this2.editableDiv);
25119
+ var endpoint = function endpoint(container, offset) {
25120
+ var probe = document.createRange();
25121
+ probe.selectNodeContents(_this2.editableDiv);
25122
+ probe.setEnd(container, offset);
25123
+ var wrapper = document.createElement("span");
25124
+ wrapper.appendChild(probe.cloneContents());
25125
+ return editableText(wrapper).length;
25126
+ };
25127
+ var from = endpoint(range.startContainer, range.startOffset);
25128
+ var to = endpoint(range.endContainer, range.endOffset);
25129
+ return {
25130
+ before: before,
25131
+ from: Math.min(from, to),
25132
+ to: Math.max(from, to)
25133
+ };
25134
+ } catch (_error) {
25135
+ return null;
25136
+ }
25137
+ });
25138
+ /** Capture an input intent before the browser mutates the contenteditable DOM. */
25139
+ _defineProperty(_this2, "handleBeforeInput", function (event) {
25140
+ try {
25141
+ var captured = transactionFromBeforeInput(_this2.editableDiv, event, _this2.compositionStart ? {
25142
+ before: _this2.compositionStart.before,
25143
+ from: _this2.compositionStart.from,
25144
+ to: _this2.compositionStart.to
25145
+ } : null);
25146
+ var id = "mri-".concat(++_this2.historySequence);
25147
+ _this2.pendingTransaction = {
25148
+ edit: captured.edit || undefined,
25149
+ intent: captured.intent,
25150
+ history: {
25151
+ id: id,
25152
+ direction: "forward"
25153
+ }
25154
+ };
25155
+ } catch (_error) {
25156
+ _this2.pendingTransaction = {
25157
+ intent: "programmatic"
25158
+ };
25159
+ }
24376
25160
  });
24377
25161
  // Comprehensive LaTeX pattern detection and auto-wrapping in math tags
24378
25162
  // Comprehensive LaTeX pattern detection for paste auto-wrapping
@@ -24509,6 +25293,14 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24509
25293
  _this2.isMouseDownInEditable = false;
24510
25294
  _this2.lastRenderedEditableHtml = null;
24511
25295
  _this2.preserveNativeDomRangeParams = null;
25296
+ // The `dangerouslySetInnerHTML` prop object, reused while the html is unchanged. React re-applies
25297
+ // the markup on a commit when it is handed a fresh object, replacing the editable's nodes and the
25298
+ // caret with them; the same object tells React there is nothing to write.
25299
+ _this2.lastEditableHtmlProp = undefined;
25300
+ _this2.lastEditableHtmlPropHtml = null;
25301
+ // Which of the field's formulas the current mouse press started on. The node itself cannot be
25302
+ // kept: focusing a field redraws it, so the element that was pressed is already gone at mouseup.
25303
+ _this2.equationEditorMouseDownKatexIndex = -1;
24512
25304
  _this2.lastSetActiveButtons = null;
24513
25305
  _this2.onChangeCallback = _this2.props.onChange;
24514
25306
 
@@ -24518,6 +25310,9 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24518
25310
  _this2.initialHistoryState = null;
24519
25311
  _this2.undoHistory = [];
24520
25312
  _this2.redoHistory = [];
25313
+ _this2.historySequence = 0;
25314
+ _this2.pendingTransaction = null;
25315
+ _this2.compositionStart = null;
24521
25316
  _this2.accent = "";
24522
25317
 
24523
25318
  // keeps track of whether user is composing in an IME - see https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
@@ -24693,6 +25488,39 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24693
25488
  endGlobalOffset: null
24694
25489
  });
24695
25490
  }
25491
+
25492
+ /** The caret's params, only when the selection is inside this field. */
25493
+ }, {
25494
+ key: "selectionRangeParams",
25495
+ value: function selectionRangeParams() {
25496
+ var selection = window.getSelection();
25497
+ if (!selection || selection.rangeCount === 0) return null;
25498
+ if (!this.editableDiv || !this.editableDiv.contains(selection.anchorNode)) {
25499
+ return null;
25500
+ }
25501
+ return this.getRangeParamsPreservingInlinePlaceholder();
25502
+ }
25503
+
25504
+ /**
25505
+ * The `dangerouslySetInnerHTML` prop, REUSED while the html is unchanged.
25506
+ *
25507
+ * React re-applies `dangerouslySetInnerHTML` on a commit when it is handed a new prop object, even
25508
+ * when the string in it is the one already in the DOM (measured with React 19: three writes of an
25509
+ * identical-length string, each replacing the editable's children — and with them the caret, which
25510
+ * then sat at the start of the field). The same object is what tells React there is nothing to
25511
+ * write, and that is how the field keeps the browser's own DOM and selection.
25512
+ */
25513
+ }, {
25514
+ key: "editableHtmlProp",
25515
+ value: function editableHtmlProp(html) {
25516
+ if (this.lastEditableHtmlProp === undefined || this.lastEditableHtmlPropHtml !== html) {
25517
+ this.lastEditableHtmlPropHtml = html;
25518
+ this.lastEditableHtmlProp = {
25519
+ __html: html
25520
+ };
25521
+ }
25522
+ return this.lastEditableHtmlProp;
25523
+ }
24696
25524
  }, {
24697
25525
  key: "getEditableHtmlForRender",
24698
25526
  value: function getEditableHtmlForRender(html) {
@@ -24721,8 +25549,12 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24721
25549
  this.preserveNativeDomRangeParams = preserveRangeParams;
24722
25550
  return liveEditableHtml;
24723
25551
  }
25552
+
25553
+ // React will write this string: it differs from the one it last rendered. Remember where the
25554
+ // caret is so it can be put back if the write takes the selection with it. A click elsewhere
25555
+ // blurs the field before the commit, so the restore below never overrides another control.
25556
+ this.preserveNativeDomRangeParams = this.state.hasFocus === true && html !== this.lastRenderedEditableHtml ? this.selectionRangeParams() : null;
24724
25557
  this.lastRenderedEditableHtml = html;
24725
- this.preserveNativeDomRangeParams = null;
24726
25558
  return html;
24727
25559
  }
24728
25560
  }, {
@@ -24954,7 +25786,19 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24954
25786
  var rangeParams = this._getRangeParams();
24955
25787
  if (rangeParams) {
24956
25788
  this.setOldRangeParams(rangeParams);
24957
- this.applyCurrentEditableDomToComponent(rangeParams);
25789
+ this.applyCurrentEditableDomToComponent(rangeParams, {
25790
+ edit: {
25791
+ before: editableText(this.editableDiv),
25792
+ from: 0,
25793
+ to: 0,
25794
+ insert: ""
25795
+ },
25796
+ intent: "format",
25797
+ history: {
25798
+ id: "mri-".concat(++this.historySequence),
25799
+ direction: "forward"
25800
+ }
25801
+ });
24958
25802
  }
24959
25803
  debugMathRichInput("moveTrailingWhitespaceOutsideInlineStyle:after", {
24960
25804
  style: style,
@@ -24970,6 +25814,7 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24970
25814
  key: "applyCurrentEditableDomToComponent",
24971
25815
  value: function applyCurrentEditableDomToComponent() {
24972
25816
  var rangeParams = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
25817
+ var transaction = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
24973
25818
  if (!this.editableDiv) return;
24974
25819
  if (rangeParams === null || rangeParams === undefined) {
24975
25820
  rangeParams = this._getRangeParams();
@@ -24984,7 +25829,8 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24984
25829
  rawText = removeEncodingIfPlainText(rawText, mimeType, this.enableHtml());
24985
25830
  rawText = this.stripEmptyInlineFormattingTags(rawText);
24986
25831
  this.applyChangesToComponent(rawText, mimeType, rangeParams, this.props.useExpertMode, this.props.selectedTab, {
24987
- skipControlledRender: true
25832
+ skipControlledRender: true,
25833
+ transaction: transaction
24988
25834
  });
24989
25835
  }
24990
25836
  }, {
@@ -25035,6 +25881,7 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
25035
25881
  });
25036
25882
  this.editableDiv.addEventListener("keydown", this.handleKeyDown);
25037
25883
  this.editableDiv.addEventListener("keyup", this.handleKeyUp);
25884
+ this.editableDiv.addEventListener("beforeinput", this.handleBeforeInput);
25038
25885
  this.editableDiv.addEventListener("paste", this.handlePaste);
25039
25886
  this.editableDiv.addEventListener("copy", this.handleCopy);
25040
25887
  if (this.props.autofocus === true) this.editableDiv.focus();
@@ -25048,6 +25895,7 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
25048
25895
  try {
25049
25896
  this.editableDiv.removeEventListener("keydown", this.handleKeyDown);
25050
25897
  this.editableDiv.removeEventListener("keyup", this.handleKeyUp);
25898
+ this.editableDiv.removeEventListener("beforeinput", this.handleBeforeInput);
25051
25899
  this.editableDiv.removeEventListener("paste", this.handlePaste);
25052
25900
  this.editableDiv.removeEventListener("copy", this.handleCopy);
25053
25901
  } catch (error) {
@@ -25208,25 +26056,77 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
25208
26056
  if (this.preserveNativeDomRangeParams !== null && this.preserveNativeDomRangeParams !== undefined && this.state.hasFocus === true) {
25209
26057
  var rangeParams = this.preserveNativeDomRangeParams;
25210
26058
  this.preserveNativeDomRangeParams = null;
25211
- debugMathRichInput("componentDidUpdate:preserve-restore-before", {
25212
- rangeParams: rangeParams,
25213
- selection: getSelectionSnapshot(this.editableDiv)
25214
- });
25215
- this._setRangeParams(rangeParams);
25216
- debugMathRichInput("componentDidUpdate:preserve-restore-after", {
25217
- selection: getSelectionSnapshot(this.editableDiv)
25218
- });
26059
+ // Only when the commit really did take the caret. A selection still in the field is the
26060
+ // user's and is not ours to move; a click elsewhere has blurred the field, which is why the
26061
+ // focus check above owns that case.
26062
+ var liveRangeParams = this._getRangeParams();
26063
+ if (liveRangeParams === null || this.rangeIsAtStart(liveRangeParams)) {
26064
+ debugMathRichInput("componentDidUpdate:preserve-restore-before", {
26065
+ rangeParams: rangeParams,
26066
+ selection: getSelectionSnapshot(this.editableDiv)
26067
+ });
26068
+ this._setRangeParams(rangeParams);
26069
+ debugMathRichInput("componentDidUpdate:preserve-restore-after", {
26070
+ selection: getSelectionSnapshot(this.editableDiv)
26071
+ });
26072
+ }
25219
26073
  }
25220
26074
  } catch (error) {
25221
26075
  console.error(error);
25222
26076
  }
25223
26077
  }
26078
+ }, {
26079
+ key: "katexNodesInField",
26080
+ value: /** The field's rendered formulas, in document order — the positions a click can land on. */
26081
+ function katexNodesInField() {
26082
+ return _toConsumableArray(this.editableDiv.querySelectorAll(".katex, .katex-error"));
26083
+ }
26084
+
26085
+ /**
26086
+ * Put the browser's selection at `range` and remember the position it resolves to.
26087
+ *
26088
+ * Used to place the caret where the component — not the browser — knows it belongs: after a formula
26089
+ * that was clicked, and at the end of a field that was focused without a click.
26090
+ */
26091
+ }, {
26092
+ key: "setCaretToRange",
26093
+ value: function setCaretToRange(range) {
26094
+ try {
26095
+ var selection = window.getSelection();
26096
+ if (!selection) return;
26097
+ selection.removeAllRanges();
26098
+ selection.addRange(range);
26099
+ var params = this._getRangeParams();
26100
+ if (params) this.setOldRangeParams(params);
26101
+ } catch (error) {
26102
+ console.error(error);
26103
+ }
26104
+ }
26105
+
26106
+ /**
26107
+ * A range that starts immediately after `node`, in the text that follows it.
26108
+ *
26109
+ * `setStartAfter` alone reads back as a position INSIDE the KaTeX subtree — the last text node of the
26110
+ * formula is the nearest text — and the text model treats that as the position BEFORE the maths. The
26111
+ * renderer always leaves a zero-width text node after a formula, so the caret starts there instead:
26112
+ * after the maths, where typing continues.
26113
+ */
26114
+ }, {
26115
+ key: "caretRangeAfterNode",
26116
+ value: function caretRangeAfterNode(node) {
26117
+ var range = document.createRange();
26118
+ var next = node.nextSibling;
26119
+ if (next !== null && next !== undefined) range.setStart(next, 0);else range.setStartAfter(node);
26120
+ range.collapse(true);
26121
+ return range;
26122
+ }
25224
26123
  }, {
25225
26124
  key: "insertAfterSmallSpace",
25226
26125
  value: function insertAfterSmallSpace(new_char, rangeParams) {
25227
26126
  try {
25228
26127
  if (rangeParams === null || rangeParams === undefined) rangeParams = this._getRangeParams();
25229
26128
  if (rangeParams.startOffset !== 0) console.warn("Unexpected startOffset " + rangeParams.startOffset + " in insertAfterSmallSpace");
26129
+ var beforeText = editableText(this.editableDiv);
25230
26130
  var node = this._findNodeWithIndex(rangeParams.startNodeIndex);
25231
26131
  if (node === null || node === undefined || node.nodeValue === null) {
25232
26132
  console.warn("Could not find small-space text node");
@@ -25251,7 +26151,20 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
25251
26151
  var new_raw_text = elementToMarkedRawText(this.editableDiv, null, 0, this.enableHtml());
25252
26152
  new_raw_text = removeEncodingIfPlainText(new_raw_text, this.props.mimeType, this.enableHtml());
25253
26153
  this.applyChangesToComponent(new_raw_text, this.props.mimeType, new_rangeParams, this.props.useExpertMode, this.props.selectedTab, {
25254
- skipControlledRender: true
26154
+ skipControlledRender: true,
26155
+ transaction: {
26156
+ edit: {
26157
+ before: beforeText,
26158
+ from: rangeParams.startGlobalOffset,
26159
+ to: rangeParams.startGlobalOffset,
26160
+ insert: new_char
26161
+ },
26162
+ intent: "input",
26163
+ history: {
26164
+ id: "mri-".concat(++this.historySequence),
26165
+ direction: "forward"
26166
+ }
26167
+ }
25255
26168
  });
25256
26169
  } catch (error) {
25257
26170
  console.error(error);
@@ -25328,6 +26241,20 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
25328
26241
  className: "MathRichInput-hint",
25329
26242
  "aria-hidden": "true"
25330
26243
  }, this.props.placeholder)), this.state.hasFocus && /*#__PURE__*/React__default["default"].createElement(Toolbar, {
26244
+ ref: function ref(r) {
26245
+ return _this3.toolbar = r;
26246
+ },
26247
+ onBlur: this.handleBlur,
26248
+ beforeAction: function beforeAction() {
26249
+ if (document.activeElement !== _this3.editableDiv) {
26250
+ var range = _this3.getOldRangeParams();
26251
+ _this3.editableDiv.focus({
26252
+ preventScroll: true
26253
+ });
26254
+ if (range !== null) _this3._setRangeParams(range);
26255
+ }
26256
+ _this3.initialiseHistory();
26257
+ },
25331
26258
  className: "Toolbar",
25332
26259
  enableMath: this.enableMath(),
25333
26260
  enableFontStyling: this.enableFontStyling(),
@@ -25360,9 +26287,7 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
25360
26287
  onBlur: this.handleBlur,
25361
26288
  onCompositionStart: this.handleCompositionStart,
25362
26289
  onCompositionEnd: this.handleCompositionEnd,
25363
- dangerouslySetInnerHTML: {
25364
- __html: editableHtml
25365
- },
26290
+ dangerouslySetInnerHTML: this.editableHtmlProp(editableHtml),
25366
26291
  "data-placeholder": this.props.placeholder || ""
25367
26292
  }))), this.state.showEquationEditor && /*#__PURE__*/React__default["default"].createElement(EquationEditorModal, {
25368
26293
  ref: function ref(r) {