@zzish/math-rich-input 0.1.57 → 0.1.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -18886,7 +18886,7 @@ var EquationEditor = /*#__PURE__*/function (_React$Component) {
18886
18886
  _classCallCheck(this, EquationEditor);
18887
18887
  _this = _callSuper(this, EquationEditor, [props]);
18888
18888
  _defineProperty(_this, "handleInsert", function () {
18889
- _this.handleInsertCallback(_this.state.latex);
18889
+ _this.handleInsertCallback(_this.latexForInsert());
18890
18890
  });
18891
18891
  _defineProperty(_this, "handleCancel", function () {
18892
18892
  _this.handleCancelCallback();
@@ -18899,8 +18899,14 @@ var EquationEditor = /*#__PURE__*/function (_React$Component) {
18899
18899
  });
18900
18900
  _defineProperty(_this, "handleKeyDown", function (e) {
18901
18901
  try {
18902
+ if (e.key === "Escape") {
18903
+ e.preventDefault();
18904
+ e.stopPropagation();
18905
+ _this.handleCancelCallback();
18906
+ return;
18907
+ }
18902
18908
  if (!_this.props.useExpertMode && e.key === "Enter") {
18903
- _this.handleInsertCallback(_this.state.latex);
18909
+ _this.handleInsertCallback(_this.latexForInsert());
18904
18910
  e.preventDefault();
18905
18911
  }
18906
18912
  } catch (error) {
@@ -19073,12 +19079,35 @@ var EquationEditor = /*#__PURE__*/function (_React$Component) {
19073
19079
  recentSymbolsCount: 0
19074
19080
  };
19075
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;
19076
19084
  _this.handleInsertCallback = props.handleInsert;
19077
19085
  _this.handleCancelCallback = props.handleCancel;
19078
19086
  return _this;
19079
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
+ */
19080
19097
  _inherits(EquationEditor, _React$Component);
19081
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
+ }, {
19082
19111
  key: "prettifyLatex",
19083
19112
  value: function prettifyLatex(latex) {
19084
19113
  // NB: replaceAll not suported on safari
@@ -19160,6 +19189,12 @@ var EquationEditor = /*#__PURE__*/function (_React$Component) {
19160
19189
  key: "mathquillDidMount",
19161
19190
  value: function mathquillDidMount(mathQuill) {
19162
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
+ }
19163
19198
  }
19164
19199
  }, {
19165
19200
  key: "componentDidMount",
@@ -19266,8 +19301,11 @@ var EquationEditor = /*#__PURE__*/function (_React$Component) {
19266
19301
  }, /*#__PURE__*/React__default["default"].createElement("div", {
19267
19302
  className: "centerBox"
19268
19303
  }, /*#__PURE__*/React__default["default"].createElement(reactMathquill.EditableMathField, {
19269
- className: "editable",
19270
- 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, " "),
19271
19309
  placeholder: "Type equation ..",
19272
19310
  config: {
19273
19311
  autoOperatorNames: AUTO_OPERATOR_NAMES
@@ -19349,6 +19387,9 @@ var EquationEditor = /*#__PURE__*/function (_React$Component) {
19349
19387
  }(React__default["default"].Component);
19350
19388
 
19351
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]';
19352
19393
  var EquationEditorModal = /*#__PURE__*/function (_React$Component) {
19353
19394
  function EquationEditorModal(props) {
19354
19395
  var _this;
@@ -19358,9 +19399,80 @@ var EquationEditorModal = /*#__PURE__*/function (_React$Component) {
19358
19399
  _this.handleInsertCallback(latex);
19359
19400
  });
19360
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
+ }
19361
19415
  _this.handleCloseCallback();
19362
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
+ });
19363
19474
  _defineProperty(_this, "focusEquationEditor", function () {
19475
+ if (!_this.mounted || _this.closing) return;
19364
19476
  console.log("🔍 Focusing equation editor...");
19365
19477
  if (_this.equationEditor) {
19366
19478
  console.log("🔍 EquationEditor ref found");
@@ -19415,15 +19527,36 @@ var EquationEditorModal = /*#__PURE__*/function (_React$Component) {
19415
19527
  }
19416
19528
  _inherits(EquationEditorModal, _React$Component);
19417
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
+ }, {
19418
19541
  key: "componentDidMount",
19419
19542
  value: function componentDidMount() {
19420
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;
19421
19548
  // Store current scroll position to prevent jumping
19422
19549
  this.originalScrollTop = window.pageYOffset || document.documentElement.scrollTop;
19423
19550
 
19424
19551
  // Create portal container if it doesn't exist
19425
19552
  this.ensurePortalContainer();
19426
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
+
19427
19560
  // Lock body scroll to prevent jumping in transform containers
19428
19561
  this.lockBodyScroll();
19429
19562
 
@@ -19431,6 +19564,7 @@ var EquationEditorModal = /*#__PURE__*/function (_React$Component) {
19431
19564
  // Need multiple RAF frames for proper CSS animation timing
19432
19565
  requestAnimationFrame(function () {
19433
19566
  requestAnimationFrame(function () {
19567
+ if (!_this2.mounted) return;
19434
19568
  _this2.setState({
19435
19569
  animationReady: true
19436
19570
  }, function () {
@@ -19446,6 +19580,11 @@ var EquationEditorModal = /*#__PURE__*/function (_React$Component) {
19446
19580
  key: "componentWillUnmount",
19447
19581
  value: function componentWillUnmount() {
19448
19582
  var _this3 = this;
19583
+ this.mounted = false;
19584
+ if (this.documentKeyListener) {
19585
+ document.removeEventListener("keydown", this.documentKeyListener, true);
19586
+ this.documentKeyListener = null;
19587
+ }
19449
19588
  // Hide portal immediately to prevent flash
19450
19589
  if (this.portalContainer) {
19451
19590
  this.portalContainer.style.visibility = "hidden";
@@ -19564,6 +19703,10 @@ var EquationEditorModal = /*#__PURE__*/function (_React$Component) {
19564
19703
  }
19565
19704
  }), /*#__PURE__*/React__default["default"].createElement("div", {
19566
19705
  className: useClass,
19706
+ role: "dialog",
19707
+ "aria-modal": "true",
19708
+ "aria-label": "Equation Editor",
19709
+ onKeyDown: this.handleKeyDown,
19567
19710
  ref: function ref(r) {
19568
19711
  return _this5.modalDiv = r;
19569
19712
  },
@@ -19921,7 +20064,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
19921
20064
  return _this2.handleButtonClick(e, character);
19922
20065
  },
19923
20066
  onClick: function onClick(e) {
19924
- return e.preventDefault();
20067
+ e.preventDefault();
20068
+ if (e.detail === 0) _this2.handleButtonClick(e, character);
19925
20069
  }
19926
20070
  }, character);
19927
20071
  })), !compactMode && /*#__PURE__*/React__default["default"].createElement("div", null, /*#__PURE__*/React__default["default"].createElement("div", {
@@ -19967,7 +20111,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
19967
20111
  return _this2.handleButtonClick(e, character);
19968
20112
  },
19969
20113
  onClick: function onClick(e) {
19970
- return e.preventDefault();
20114
+ e.preventDefault();
20115
+ if (e.detail === 0) _this2.handleButtonClick(e, character);
19971
20116
  }
19972
20117
  }, character);
19973
20118
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -19985,7 +20130,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
19985
20130
  return _this2.handleButtonClick(e, character, true);
19986
20131
  },
19987
20132
  onClick: function onClick(e) {
19988
- return e.preventDefault();
20133
+ e.preventDefault();
20134
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
19989
20135
  }
19990
20136
  }, character);
19991
20137
  }))), this.state.selectedTab === "Greek" && /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20005,7 +20151,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20005
20151
  return _this2.handleButtonClick(e, character, true);
20006
20152
  },
20007
20153
  onClick: function onClick(e) {
20008
- return e.preventDefault();
20154
+ e.preventDefault();
20155
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20009
20156
  }
20010
20157
  }, character);
20011
20158
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20023,7 +20170,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20023
20170
  return _this2.handleButtonClick(e, character, true);
20024
20171
  },
20025
20172
  onClick: function onClick(e) {
20026
- return e.preventDefault();
20173
+ e.preventDefault();
20174
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20027
20175
  }
20028
20176
  }, character);
20029
20177
  }))), this.state.selectedTab === "Math" && /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20043,7 +20191,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20043
20191
  return _this2.handleButtonClick(e, character, true);
20044
20192
  },
20045
20193
  onClick: function onClick(e) {
20046
- return e.preventDefault();
20194
+ e.preventDefault();
20195
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20047
20196
  }
20048
20197
  }, character);
20049
20198
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20061,7 +20210,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20061
20210
  return _this2.handleButtonClick(e, character, true);
20062
20211
  },
20063
20212
  onClick: function onClick(e) {
20064
- return e.preventDefault();
20213
+ e.preventDefault();
20214
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20065
20215
  }
20066
20216
  }, character);
20067
20217
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20079,7 +20229,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20079
20229
  return _this2.handleButtonClick(e, character, true);
20080
20230
  },
20081
20231
  onClick: function onClick(e) {
20082
- return e.preventDefault();
20232
+ e.preventDefault();
20233
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20083
20234
  }
20084
20235
  }, character);
20085
20236
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20097,7 +20248,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20097
20248
  return _this2.handleButtonClick(e, character, true);
20098
20249
  },
20099
20250
  onClick: function onClick(e) {
20100
- return e.preventDefault();
20251
+ e.preventDefault();
20252
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20101
20253
  }
20102
20254
  }, character);
20103
20255
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20115,7 +20267,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20115
20267
  return _this2.handleButtonClick(e, character, true);
20116
20268
  },
20117
20269
  onClick: function onClick(e) {
20118
- return e.preventDefault();
20270
+ e.preventDefault();
20271
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20119
20272
  }
20120
20273
  }, character);
20121
20274
  }))), this.state.selectedTab === "Emoticons" && /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20135,7 +20288,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20135
20288
  return _this2.handleButtonClick(e, character, true);
20136
20289
  },
20137
20290
  onClick: function onClick(e) {
20138
- return e.preventDefault();
20291
+ e.preventDefault();
20292
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20139
20293
  }
20140
20294
  }, character);
20141
20295
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20153,7 +20307,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20153
20307
  return _this2.handleButtonClick(e, character, true);
20154
20308
  },
20155
20309
  onClick: function onClick(e) {
20156
- return e.preventDefault();
20310
+ e.preventDefault();
20311
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20157
20312
  }
20158
20313
  }, character);
20159
20314
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20171,7 +20326,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20171
20326
  return _this2.handleButtonClick(e, character, true);
20172
20327
  },
20173
20328
  onClick: function onClick(e) {
20174
- return e.preventDefault();
20329
+ e.preventDefault();
20330
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20175
20331
  }
20176
20332
  }, character);
20177
20333
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20189,7 +20345,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20189
20345
  return _this2.handleButtonClick(e, character, true);
20190
20346
  },
20191
20347
  onClick: function onClick(e) {
20192
- return e.preventDefault();
20348
+ e.preventDefault();
20349
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20193
20350
  }
20194
20351
  }, character);
20195
20352
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20207,7 +20364,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20207
20364
  return _this2.handleButtonClick(e, character, true);
20208
20365
  },
20209
20366
  onClick: function onClick(e) {
20210
- return e.preventDefault();
20367
+ e.preventDefault();
20368
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20211
20369
  }
20212
20370
  }, character);
20213
20371
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20225,7 +20383,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20225
20383
  return _this2.handleButtonClick(e, character, true);
20226
20384
  },
20227
20385
  onClick: function onClick(e) {
20228
- return e.preventDefault();
20386
+ e.preventDefault();
20387
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20229
20388
  }
20230
20389
  }, character);
20231
20390
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20243,7 +20402,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20243
20402
  return _this2.handleButtonClick(e, character, true);
20244
20403
  },
20245
20404
  onClick: function onClick(e) {
20246
- return e.preventDefault();
20405
+ e.preventDefault();
20406
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20247
20407
  }
20248
20408
  }, character);
20249
20409
  }))), this.state.selectedTab === "More" && /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20263,7 +20423,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20263
20423
  return _this2.handleButtonClick(e, character, true);
20264
20424
  },
20265
20425
  onClick: function onClick(e) {
20266
- return e.preventDefault();
20426
+ e.preventDefault();
20427
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20267
20428
  }
20268
20429
  }, character);
20269
20430
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20281,7 +20442,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20281
20442
  return _this2.handleButtonClick(e, character, true);
20282
20443
  },
20283
20444
  onClick: function onClick(e) {
20284
- return e.preventDefault();
20445
+ e.preventDefault();
20446
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20285
20447
  }
20286
20448
  }, character);
20287
20449
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20299,7 +20461,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20299
20461
  return _this2.handleButtonClick(e, character, true);
20300
20462
  },
20301
20463
  onClick: function onClick(e) {
20302
- return e.preventDefault();
20464
+ e.preventDefault();
20465
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20303
20466
  }
20304
20467
  }, character);
20305
20468
  })), /*#__PURE__*/React__default["default"].createElement("div", {
@@ -20317,7 +20480,8 @@ var AccentBar = /*#__PURE__*/function (_React$Component) {
20317
20480
  return _this2.handleButtonClick(e, character, true);
20318
20481
  },
20319
20482
  onClick: function onClick(e) {
20320
- return e.preventDefault();
20483
+ e.preventDefault();
20484
+ if (e.detail === 0) _this2.handleButtonClick(e, character, true);
20321
20485
  }
20322
20486
  }, character);
20323
20487
  })))))));
@@ -20353,8 +20517,10 @@ var Toolbar = /*#__PURE__*/function (_React$Component) {
20353
20517
  });
20354
20518
  });
20355
20519
  _defineProperty(_this, "handleButtonClick", function (event, buttonName) {
20520
+ var _this$props$beforeAct, _this$props;
20356
20521
  event.preventDefault();
20357
20522
  event.stopPropagation();
20523
+ (_this$props$beforeAct = (_this$props = _this.props).beforeAction) === null || _this$props$beforeAct === void 0 || _this$props$beforeAct.call(_this$props);
20358
20524
  _this.suppliedClickHandler(event, buttonName);
20359
20525
  });
20360
20526
  _defineProperty(_this, "handleAccentButtonClick", function (event, oldCharacter, newCharacter) {
@@ -20453,87 +20619,89 @@ var Toolbar = /*#__PURE__*/function (_React$Component) {
20453
20619
  style: style,
20454
20620
  ref: function ref(r) {
20455
20621
  return _this2.toolbar = r;
20456
- }
20622
+ },
20623
+ onBlur: this.props.onBlur
20457
20624
  }, enableFontStyling && /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement("button", {
20625
+ type: "button",
20458
20626
  name: "Bold",
20627
+ "aria-label": "Bold",
20459
20628
  className: "Toolbar-button TB-narrow" + (this.props.activeButtons.bold ? " TB-selected" : ""),
20460
20629
  onClick: function onClick(e) {
20461
- return e.preventDefault();
20630
+ return _this2.handleButtonClick(e, "Bold");
20462
20631
  },
20463
20632
  onMouseDown: function onMouseDown(e) {
20464
20633
  return e.preventDefault();
20465
- },
20466
- onMouseUp: function onMouseUp(e) {
20467
- return _this2.handleButtonClick(e, "Bold");
20468
20634
  }
20469
20635
  }, /*#__PURE__*/React__default["default"].createElement("b", null, "B")), /*#__PURE__*/React__default["default"].createElement("button", {
20636
+ type: "button",
20470
20637
  name: "Italic",
20638
+ "aria-label": "Italic",
20471
20639
  className: "Toolbar-button TB-narrow" + (this.props.activeButtons.italic ? " TB-selected" : ""),
20472
20640
  onClick: function onClick(e) {
20473
- return e.preventDefault();
20641
+ return _this2.handleButtonClick(e, "Italic");
20474
20642
  },
20475
20643
  onMouseDown: function onMouseDown(e) {
20476
20644
  return e.preventDefault();
20477
- },
20478
- onMouseUp: function onMouseUp(e) {
20479
- return _this2.handleButtonClick(e, "Italic");
20480
20645
  }
20481
20646
  }, /*#__PURE__*/React__default["default"].createElement("i", null, "I")), /*#__PURE__*/React__default["default"].createElement("button", {
20647
+ type: "button",
20482
20648
  name: "Underline",
20649
+ "aria-label": "Underline",
20483
20650
  className: "Toolbar-button TB-narrow" + (this.props.activeButtons.underline ? " TB-selected" : ""),
20484
20651
  onClick: function onClick(e) {
20485
- return e.preventDefault();
20652
+ return _this2.handleButtonClick(e, "Underline");
20486
20653
  },
20487
20654
  onMouseDown: function onMouseDown(e) {
20488
20655
  return e.preventDefault();
20489
- },
20490
- onMouseUp: function onMouseUp(e) {
20491
- return _this2.handleButtonClick(e, "Underline");
20492
20656
  }
20493
20657
  }, /*#__PURE__*/React__default["default"].createElement("u", null, "U")), /*#__PURE__*/React__default["default"].createElement("button", {
20658
+ type: "button",
20494
20659
  name: "Superscript",
20660
+ "aria-label": "Superscript",
20495
20661
  className: "Toolbar-button TB-narrow" + (this.props.activeButtons.superscript ? " TB-selected" : ""),
20496
20662
  onClick: function onClick(e) {
20497
- return e.preventDefault();
20663
+ return _this2.handleButtonClick(e, "Superscript");
20498
20664
  },
20499
20665
  onMouseDown: function onMouseDown(e) {
20500
20666
  return e.preventDefault();
20501
- },
20502
- onMouseUp: function onMouseUp(e) {
20503
- return _this2.handleButtonClick(e, "Superscript");
20504
20667
  }
20505
20668
  }, "a", /*#__PURE__*/React__default["default"].createElement("sup", null, "x")), /*#__PURE__*/React__default["default"].createElement("button", {
20669
+ type: "button",
20506
20670
  name: "Subscript",
20671
+ "aria-label": "Subscript",
20507
20672
  className: "Toolbar-button TB-narrow" + (this.props.activeButtons.subscript ? " TB-selected" : ""),
20508
20673
  onClick: function onClick(e) {
20509
- return e.preventDefault();
20674
+ return _this2.handleButtonClick(e, "Subscript");
20510
20675
  },
20511
20676
  onMouseDown: function onMouseDown(e) {
20512
20677
  return e.preventDefault();
20513
- },
20514
- onMouseUp: function onMouseUp(e) {
20515
- return _this2.handleButtonClick(e, "Subscript");
20516
20678
  }
20517
20679
  }, "a", /*#__PURE__*/React__default["default"].createElement("sub", null, "x"))), /*#__PURE__*/React__default["default"].createElement("button", {
20680
+ type: "button",
20518
20681
  name: "Accents",
20682
+ "aria-label": "Accents",
20683
+ "aria-expanded": this.state.showAccents,
20519
20684
  className: "Toolbar-button TB-narrow" + (this.state.showAccents ? " TB-selected" : ""),
20520
20685
  onMouseDown: function onMouseDown(e) {
20521
20686
  return e.preventDefault();
20522
20687
  },
20523
20688
  onClick: function onClick(e) {
20524
- return e.preventDefault();
20525
- },
20526
- onMouseUp: function onMouseUp(e) {
20527
- 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);
20528
20694
  }
20529
20695
  }, "\xE1"), enableMath && /*#__PURE__*/React__default["default"].createElement("button", {
20696
+ type: "button",
20530
20697
  name: "Equation",
20698
+ "aria-label": "Insert equation",
20531
20699
  className: "Toolbar-button TB-wide",
20532
20700
  onClick: function onClick(e) {
20533
- return e.preventDefault();
20701
+ return _this2.handleButtonClick(e, "Equation");
20534
20702
  },
20535
20703
  onMouseDown: function onMouseDown(e) {
20536
- return _this2.handleButtonClick(e, "Equation");
20704
+ return e.preventDefault();
20537
20705
  }
20538
20706
  }, /*#__PURE__*/React__default["default"].createElement("div", {
20539
20707
  dangerouslySetInnerHTML: {
@@ -20825,6 +20993,8 @@ function getMark() {
20825
20993
  return MARK$1;
20826
20994
  }
20827
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;
20828
20998
  return node.className === "katex" || node.className === "katex-error";
20829
20999
  }
20830
21000
  function isTextNode(node) {
@@ -21530,7 +21700,6 @@ function getRangeParams(editableDiv) {
21530
21700
  }
21531
21701
  var selection = window.getSelection();
21532
21702
  if (selection.rangeCount === 0) {
21533
- console.error("Can't get range params, window.getSelection did not return any ranges");
21534
21703
  return null;
21535
21704
  }
21536
21705
  var range = selection.getRangeAt(0);
@@ -23526,6 +23695,9 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
23526
23695
  try {
23527
23696
  // console.log("handleInput")
23528
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;
23529
23701
  var transaction = _this2.pendingTransaction;
23530
23702
  _this2.pendingTransaction = null;
23531
23703
  debugMathRichInput("handleInput:start", {
@@ -24006,7 +24178,9 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24006
24178
  var moveCursorOnInsert = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
24007
24179
  var node = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
24008
24180
  try {
24181
+ var _node;
24009
24182
  var rangeParams = _this2._getRangeParams();
24183
+ var hasNode = node !== null && node !== undefined;
24010
24184
  debugMathRichInput("showEquationEditor:start", {
24011
24185
  moveCursorOnInsert: moveCursorOnInsert,
24012
24186
  nodeProvided: Boolean(node),
@@ -24015,8 +24189,27 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24015
24189
  innerHTML: _this2.editableDiv ? _this2.editableDiv.innerHTML : null,
24016
24190
  selection: getSelectionSnapshot(_this2.editableDiv)
24017
24191
  });
24018
- if (rangeParams.startNodeIndex < 0) {
24019
- 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
+ });
24020
24213
  _this2.setOldRangeParams({
24021
24214
  startNodeIndex: 2,
24022
24215
  startOffset: 1,
@@ -24024,6 +24217,9 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24024
24217
  endOffset: 1
24025
24218
  });
24026
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;
24027
24223
  _this2.moveCursorOnInsert = moveCursorOnInsert;
24028
24224
  _this2.setState({
24029
24225
  showEquationEditor: true,
@@ -24033,7 +24229,14 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24033
24229
  }
24034
24230
  var latex = "";
24035
24231
  var offset = 0;
24036
- 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
+ }
24037
24240
 
24038
24241
  // console.log(rangeParams)
24039
24242
  // console.log(this.editableDiv.childNodes)
@@ -24053,7 +24256,7 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24053
24256
  _this2.editingNode = node;
24054
24257
  } else {
24055
24258
  latex = "";
24056
- offset = rangeParams.startOffset;
24259
+ offset = rangeParams ? rangeParams.startOffset : 0;
24057
24260
  _this2.editingExistingEquation = false;
24058
24261
  _this2.editingNode = null;
24059
24262
  }
@@ -24081,6 +24284,7 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24081
24284
  _this2.isMouseDownInEditable = false;
24082
24285
  }, 0);
24083
24286
  _this2.showEquationEditorOnMouseUp = false;
24287
+ _this2.equationEditorMouseDownKatexIndex = -1;
24084
24288
  var node = null;
24085
24289
  try {
24086
24290
  node = event.target;
@@ -24096,6 +24300,11 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24096
24300
  return;
24097
24301
  }
24098
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);
24099
24308
  // Save current range parameters so that we can restore them later if we wish
24100
24309
  _this2.setOldRangeParams(_this2._getRangeParams());
24101
24310
 
@@ -24114,6 +24323,25 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24114
24323
 
24115
24324
  // Clear selection anchor on mouse click
24116
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
+ }
24117
24345
 
24118
24346
  // A click that lands with the selection outside this field — a toolbar button, another field —
24119
24347
  // gives no range at all. Dereferenced unguarded, this threw on every such click.
@@ -24126,11 +24354,6 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24126
24354
  console.warn("Start or end node was null");
24127
24355
  return;
24128
24356
  }
24129
- if (params.startNodeIndex === params.endNodeIndex && params.startOffset === params.endOffset && _this2.showEquationEditorOnMouseUp) {
24130
- _this2.editableDiv.focus();
24131
- _this2.showEquationEditor(false, startNode);
24132
- return;
24133
- }
24134
24357
 
24135
24358
  // If our range starts or ends inside a katex node, move it out
24136
24359
  var new_rangeParams = null;
@@ -24151,6 +24374,10 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24151
24374
  new_rangeParams.endOffset = SMALL_SPACE_LENGTH;
24152
24375
  }
24153
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;
24154
24381
  _this2._setRangeParams(new_rangeParams);
24155
24382
  event.preventDefault();
24156
24383
  return;
@@ -24184,6 +24411,27 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24184
24411
  innerHTML: _this2.editableDiv ? _this2.editableDiv.innerHTML : null,
24185
24412
  selection: getSelectionSnapshot(_this2.editableDiv)
24186
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
+ }
24187
24435
  var start_pos = _this2.markedRawText.indexOf(MARK);
24188
24436
  var end_pos = _this2.markedRawText.indexOf(MARK, start_pos + MARK.length);
24189
24437
  var oldRangeParams = _this2.getOldRangeParams();
@@ -24304,6 +24552,9 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24304
24552
  // }
24305
24553
  _defineProperty(_this2, "styleText", function (event, style) {
24306
24554
  try {
24555
+ _this2.initialiseHistory();
24556
+ _this2.resetHistoryRedo();
24557
+ _this2.formatCommandActive = true;
24307
24558
  style = style.toLowerCase();
24308
24559
  var command = _this2.getStyleCommand(style);
24309
24560
  var activeBefore = _this2.getActiveButtonsFromSelection();
@@ -24311,12 +24562,14 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24311
24562
  var isCollapsed = selection === null || selection.rangeCount === 0 || selection.getRangeAt(0).collapsed;
24312
24563
  var success = false;
24313
24564
  var insertedTypingStylePlaceholder = false;
24565
+ var publishedByWhitespaceMove = false;
24314
24566
 
24315
24567
  // This first part is a workaround whereby executing subscript or superscript
24316
24568
  // within an existing subscript or superscript makes the text even smaller instead
24317
24569
  // of toggling it off. Thus we manually toggle it off with some jigerry pokery!
24318
24570
  if (isCollapsed && activeBefore[style] && _this2.moveTrailingWhitespaceOutsideInlineStyle(style, command)) {
24319
24571
  success = true;
24572
+ publishedByWhitespaceMove = true;
24320
24573
  } else if (isCollapsed && !activeBefore[style] && selection && selection.rangeCount > 0) {
24321
24574
  success = _this2.insertInlineStylePlaceholder(style);
24322
24575
  insertedTypingStylePlaceholder = success;
@@ -24339,7 +24592,7 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24339
24592
  }
24340
24593
  var rangeParams = _this2._getRangeParams();
24341
24594
  if (!insertedTypingStylePlaceholder) _this2.cleanupEmptyInlineFormattingElements();
24342
- if (rangeParams !== null && rangeParams !== undefined) {
24595
+ if (!publishedByWhitespaceMove && rangeParams !== null && rangeParams !== undefined) {
24343
24596
  _this2._setRangeParams(rangeParams);
24344
24597
  _this2.applyCurrentEditableDomToComponent(rangeParams, {
24345
24598
  edit: {
@@ -24359,6 +24612,8 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24359
24612
  return success;
24360
24613
  } catch (error) {
24361
24614
  console.error(error);
24615
+ } finally {
24616
+ _this2.formatCommandActive = false;
24362
24617
  }
24363
24618
  });
24364
24619
  _defineProperty(_this2, "handleToolbarButtonClick", function (event, buttonName) {
@@ -24750,12 +25005,21 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24750
25005
  currentRangeParams = null;
24751
25006
  }
24752
25007
  var savedRangeParams = _this2.getOldRangeParams();
24753
- 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);
24754
25017
  debugMathRichInput("handleFocus:start", {
24755
25018
  propsValue: _this2.props.value,
24756
25019
  oldRangeParams: _this2.oldRangeParams,
24757
25020
  currentRangeParams: currentRangeParams,
24758
25021
  shouldRestoreSavedRange: shouldRestoreSavedRange,
25022
+ shouldMoveToEnd: shouldMoveToEnd,
24759
25023
  innerHTML: _this2.editableDiv ? _this2.editableDiv.innerHTML : null,
24760
25024
  selection: getSelectionSnapshot(_this2.editableDiv)
24761
25025
  });
@@ -24769,6 +25033,22 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24769
25033
  if (shouldRestoreSavedRange) {
24770
25034
  _this2._setRangeParams(savedRangeParams);
24771
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);
24772
25052
  }
24773
25053
  debugMathRichInput("handleFocus:after-setState", {
24774
25054
  selection: getSelectionSnapshot(_this2.editableDiv)
@@ -24779,11 +25059,12 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24779
25059
  }
24780
25060
  });
24781
25061
  _defineProperty(_this2, "handleBlur", function (event) {
25062
+ var _this2$toolbar;
24782
25063
  var onBlur = _this2.props.onBlur;
24783
25064
  var currentRangeParams = null;
24784
25065
  try {
24785
25066
  currentRangeParams = _this2._getRangeParams();
24786
- if (_this2.rangeHasNonZeroPosition(currentRangeParams)) {
25067
+ if (currentRangeParams !== null) {
24787
25068
  _this2.setOldRangeParams(currentRangeParams);
24788
25069
  }
24789
25070
  } catch (error) {
@@ -24796,6 +25077,9 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
24796
25077
  innerHTML: _this2.editableDiv ? _this2.editableDiv.innerHTML : null,
24797
25078
  selection: getSelectionSnapshot(_this2.editableDiv)
24798
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;
24799
25083
  if (onBlur != null) {
24800
25084
  onBlur();
24801
25085
  }
@@ -25009,6 +25293,14 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
25009
25293
  _this2.isMouseDownInEditable = false;
25010
25294
  _this2.lastRenderedEditableHtml = null;
25011
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;
25012
25304
  _this2.lastSetActiveButtons = null;
25013
25305
  _this2.onChangeCallback = _this2.props.onChange;
25014
25306
 
@@ -25196,6 +25488,39 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
25196
25488
  endGlobalOffset: null
25197
25489
  });
25198
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
+ }
25199
25524
  }, {
25200
25525
  key: "getEditableHtmlForRender",
25201
25526
  value: function getEditableHtmlForRender(html) {
@@ -25224,8 +25549,12 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
25224
25549
  this.preserveNativeDomRangeParams = preserveRangeParams;
25225
25550
  return liveEditableHtml;
25226
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;
25227
25557
  this.lastRenderedEditableHtml = html;
25228
- this.preserveNativeDomRangeParams = null;
25229
25558
  return html;
25230
25559
  }
25231
25560
  }, {
@@ -25727,19 +26056,70 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
25727
26056
  if (this.preserveNativeDomRangeParams !== null && this.preserveNativeDomRangeParams !== undefined && this.state.hasFocus === true) {
25728
26057
  var rangeParams = this.preserveNativeDomRangeParams;
25729
26058
  this.preserveNativeDomRangeParams = null;
25730
- debugMathRichInput("componentDidUpdate:preserve-restore-before", {
25731
- rangeParams: rangeParams,
25732
- selection: getSelectionSnapshot(this.editableDiv)
25733
- });
25734
- this._setRangeParams(rangeParams);
25735
- debugMathRichInput("componentDidUpdate:preserve-restore-after", {
25736
- selection: getSelectionSnapshot(this.editableDiv)
25737
- });
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
+ }
25738
26073
  }
25739
26074
  } catch (error) {
25740
26075
  console.error(error);
25741
26076
  }
25742
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
+ }
25743
26123
  }, {
25744
26124
  key: "insertAfterSmallSpace",
25745
26125
  value: function insertAfterSmallSpace(new_char, rangeParams) {
@@ -25861,6 +26241,20 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
25861
26241
  className: "MathRichInput-hint",
25862
26242
  "aria-hidden": "true"
25863
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
+ },
25864
26258
  className: "Toolbar",
25865
26259
  enableMath: this.enableMath(),
25866
26260
  enableFontStyling: this.enableFontStyling(),
@@ -25893,9 +26287,7 @@ var MathRichInput = /*#__PURE__*/function (_React$Component) {
25893
26287
  onBlur: this.handleBlur,
25894
26288
  onCompositionStart: this.handleCompositionStart,
25895
26289
  onCompositionEnd: this.handleCompositionEnd,
25896
- dangerouslySetInnerHTML: {
25897
- __html: editableHtml
25898
- },
26290
+ dangerouslySetInnerHTML: this.editableHtmlProp(editableHtml),
25899
26291
  "data-placeholder": this.props.placeholder || ""
25900
26292
  }))), this.state.showEquationEditor && /*#__PURE__*/React__default["default"].createElement(EquationEditorModal, {
25901
26293
  ref: function ref(r) {