@barocss/math-editor 0.2.1 → 0.5.0

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.
Files changed (202) hide show
  1. package/ADAPTERS.md +25 -14
  2. package/API-JAVASCRIPT.md +1 -1
  3. package/API-REACT.md +5 -1
  4. package/API-SESSION.md +178 -6
  5. package/API-SOLID.md +1 -1
  6. package/API-SVELTE.md +1 -1
  7. package/API-VUE.md +1 -1
  8. package/API-WEB-COMPONENT.md +3 -1
  9. package/CHANGELOG.md +106 -0
  10. package/EDITING-SCENARIOS.md +174 -0
  11. package/EMBEDDING.md +4 -0
  12. package/IMPLEMENTATION.md +122 -2
  13. package/JSON-MODEL.md +1 -0
  14. package/LATEX-GUIDE.md +30 -1
  15. package/LATEX-MODEL.md +12 -0
  16. package/LATEX-SCOPE.md +12 -3
  17. package/LICENSE +1 -3
  18. package/README.md +201 -39
  19. package/RELEASING.md +81 -10
  20. package/RENDERING-TESTS.md +79 -0
  21. package/ROADMAP.md +220 -11
  22. package/STYLING.md +157 -0
  23. package/SUPPORT.md +24 -12
  24. package/VALIDATION.md +444 -4
  25. package/dist/context-tools.d.ts +21 -0
  26. package/dist/context-tools.js +37 -0
  27. package/dist/core.d.ts +5 -1
  28. package/dist/core.js +5 -1
  29. package/dist/document-codec.d.ts +0 -1
  30. package/dist/document-codec.js +0 -1
  31. package/dist/dom/caret-geometry.d.ts +3 -0
  32. package/dist/dom/caret-geometry.js +41 -0
  33. package/dist/dom/context-keyboard.d.ts +2 -0
  34. package/dist/dom/context-keyboard.js +22 -0
  35. package/dist/dom/elements.d.ts +1 -2
  36. package/dist/dom/elements.js +2 -3
  37. package/dist/dom/latex-panel.d.ts +10 -0
  38. package/dist/dom/latex-panel.js +59 -0
  39. package/dist/dom/matrix-selection.d.ts +11 -0
  40. package/dist/dom/matrix-selection.js +30 -0
  41. package/dist/dom/menu-position.d.ts +17 -2
  42. package/dist/dom/menu-position.js +129 -16
  43. package/dist/dom/presentation-controls.d.ts +4 -0
  44. package/dist/dom/presentation-controls.js +56 -0
  45. package/dist/dom/quick-panel.d.ts +12 -0
  46. package/dist/dom/quick-panel.js +102 -0
  47. package/dist/dom/selection.d.ts +29 -0
  48. package/dist/dom/selection.js +53 -0
  49. package/dist/dom/symbol-panel.d.ts +9 -0
  50. package/dist/dom/symbol-panel.js +146 -0
  51. package/dist/dom/theme.d.ts +6 -0
  52. package/dist/dom/theme.js +68 -0
  53. package/dist/dom/toolbar-catalog.d.ts +10 -0
  54. package/dist/dom/toolbar-catalog.js +35 -0
  55. package/dist/dom/toolbar.d.ts +13 -2
  56. package/dist/dom/toolbar.js +295 -28
  57. package/dist/dom.d.ts +9 -1
  58. package/dist/dom.js +682 -151
  59. package/dist/editor-labels.d.ts +0 -1
  60. package/dist/editor-labels.js +0 -1
  61. package/dist/enter-policy.d.ts +0 -1
  62. package/dist/enter-policy.js +0 -1
  63. package/dist/fences.d.ts +0 -1
  64. package/dist/fences.js +0 -1
  65. package/dist/i18n.d.ts +0 -1
  66. package/dist/i18n.js +0 -1
  67. package/dist/index.d.ts +4 -1
  68. package/dist/index.js +4 -1
  69. package/dist/latex-insertion.d.ts +12 -0
  70. package/dist/latex-insertion.js +26 -0
  71. package/dist/latex-paste-panel.d.ts +3 -0
  72. package/dist/latex-paste-panel.js +18 -0
  73. package/dist/latex.d.ts +0 -1
  74. package/dist/latex.js +11 -1
  75. package/dist/lines.d.ts +0 -1
  76. package/dist/lines.js +0 -1
  77. package/dist/locales/en.js +39 -1
  78. package/dist/locales/en.json +39 -1
  79. package/dist/locales/ko.js +39 -1
  80. package/dist/locales/ko.json +39 -1
  81. package/dist/math-editor-toolbar.d.ts +4 -2
  82. package/dist/math-editor-toolbar.js +3 -4
  83. package/dist/math-editor.d.ts +7 -2
  84. package/dist/math-editor.js +661 -308
  85. package/dist/math-layout.d.ts +12 -0
  86. package/dist/math-layout.js +59 -0
  87. package/dist/math-spacing.d.ts +13 -0
  88. package/dist/math-spacing.js +87 -0
  89. package/dist/matrix-range.d.ts +40 -0
  90. package/dist/matrix-range.js +242 -0
  91. package/dist/matrix.d.ts +0 -1
  92. package/dist/matrix.js +0 -1
  93. package/dist/model.d.ts +4 -1
  94. package/dist/model.js +49 -5
  95. package/dist/outputs.d.ts +0 -1
  96. package/dist/outputs.js +0 -1
  97. package/dist/preferences.d.ts +24 -0
  98. package/dist/preferences.js +66 -0
  99. package/dist/presentation-controls.d.ts +10 -0
  100. package/dist/presentation-controls.js +10 -0
  101. package/dist/presentation.d.ts +13 -0
  102. package/dist/presentation.js +86 -0
  103. package/dist/quick-panel.d.ts +2 -0
  104. package/dist/quick-panel.js +17 -0
  105. package/dist/range.d.ts +2 -1
  106. package/dist/range.js +11 -1
  107. package/dist/react.d.ts +0 -1
  108. package/dist/react.js +0 -1
  109. package/dist/root-transform.d.ts +19 -0
  110. package/dist/root-transform.js +69 -0
  111. package/dist/session.d.ts +31 -2
  112. package/dist/session.js +108 -7
  113. package/dist/solid.d.ts +0 -1
  114. package/dist/solid.js +0 -1
  115. package/dist/suggestions.d.ts +13 -1
  116. package/dist/suggestions.js +153 -12
  117. package/dist/svelte.d.ts +0 -1
  118. package/dist/svelte.js +0 -1
  119. package/dist/symbol-browser.d.ts +0 -1
  120. package/dist/symbol-browser.js +0 -1
  121. package/dist/symbols.d.ts +0 -1
  122. package/dist/symbols.js +0 -1
  123. package/dist/templates.d.ts +0 -1
  124. package/dist/templates.js +0 -1
  125. package/dist/tokens.d.ts +0 -1
  126. package/dist/tokens.js +0 -1
  127. package/dist/vertical-navigation.d.ts +15 -0
  128. package/dist/vertical-navigation.js +145 -0
  129. package/dist/vue.d.ts +0 -1
  130. package/dist/vue.js +0 -1
  131. package/dist/web-component.d.ts +0 -1
  132. package/dist/web-component.js +9 -2
  133. package/package.json +11 -3
  134. package/src/fonts/KaTeX_Math-Italic.woff2 +0 -0
  135. package/src/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  136. package/src/fonts/README.md +18 -0
  137. package/src/shapes/README.md +21 -0
  138. package/src/shapes/parenthesis-bottom.svg +1 -0
  139. package/src/shapes/parenthesis-top.svg +1 -0
  140. package/src/shapes/parenthesis.svg +1 -0
  141. package/src/shapes/radical.svg +1 -0
  142. package/src/style.css +949 -173
  143. package/dist/core.d.ts.map +0 -1
  144. package/dist/core.js.map +0 -1
  145. package/dist/document-codec.d.ts.map +0 -1
  146. package/dist/document-codec.js.map +0 -1
  147. package/dist/dom/elements.d.ts.map +0 -1
  148. package/dist/dom/elements.js.map +0 -1
  149. package/dist/dom/menu-position.d.ts.map +0 -1
  150. package/dist/dom/menu-position.js.map +0 -1
  151. package/dist/dom/toolbar.d.ts.map +0 -1
  152. package/dist/dom/toolbar.js.map +0 -1
  153. package/dist/dom.d.ts.map +0 -1
  154. package/dist/dom.js.map +0 -1
  155. package/dist/editor-labels.d.ts.map +0 -1
  156. package/dist/editor-labels.js.map +0 -1
  157. package/dist/enter-policy.d.ts.map +0 -1
  158. package/dist/enter-policy.js.map +0 -1
  159. package/dist/fences.d.ts.map +0 -1
  160. package/dist/fences.js.map +0 -1
  161. package/dist/i18n.d.ts.map +0 -1
  162. package/dist/i18n.js.map +0 -1
  163. package/dist/index.d.ts.map +0 -1
  164. package/dist/index.js.map +0 -1
  165. package/dist/latex.d.ts.map +0 -1
  166. package/dist/latex.js.map +0 -1
  167. package/dist/lines.d.ts.map +0 -1
  168. package/dist/lines.js.map +0 -1
  169. package/dist/math-editor-toolbar.d.ts.map +0 -1
  170. package/dist/math-editor-toolbar.js.map +0 -1
  171. package/dist/math-editor.d.ts.map +0 -1
  172. package/dist/math-editor.js.map +0 -1
  173. package/dist/matrix.d.ts.map +0 -1
  174. package/dist/matrix.js.map +0 -1
  175. package/dist/model.d.ts.map +0 -1
  176. package/dist/model.js.map +0 -1
  177. package/dist/outputs.d.ts.map +0 -1
  178. package/dist/outputs.js.map +0 -1
  179. package/dist/range.d.ts.map +0 -1
  180. package/dist/range.js.map +0 -1
  181. package/dist/react.d.ts.map +0 -1
  182. package/dist/react.js.map +0 -1
  183. package/dist/session.d.ts.map +0 -1
  184. package/dist/session.js.map +0 -1
  185. package/dist/solid.d.ts.map +0 -1
  186. package/dist/solid.js.map +0 -1
  187. package/dist/suggestions.d.ts.map +0 -1
  188. package/dist/suggestions.js.map +0 -1
  189. package/dist/svelte.d.ts.map +0 -1
  190. package/dist/svelte.js.map +0 -1
  191. package/dist/symbol-browser.d.ts.map +0 -1
  192. package/dist/symbol-browser.js.map +0 -1
  193. package/dist/symbols.d.ts.map +0 -1
  194. package/dist/symbols.js.map +0 -1
  195. package/dist/templates.d.ts.map +0 -1
  196. package/dist/templates.js.map +0 -1
  197. package/dist/tokens.d.ts.map +0 -1
  198. package/dist/tokens.js.map +0 -1
  199. package/dist/vue.d.ts.map +0 -1
  200. package/dist/vue.js.map +0 -1
  201. package/dist/web-component.d.ts.map +0 -1
  202. package/dist/web-component.js.map +0 -1
@@ -1,32 +1,55 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { positionMathMenu } from './dom/menu-position.js';
3
- import { fences } from './fences.js';
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { mathLayout } from './math-layout.js';
3
+ import { transformRoot } from './root-transform.js';
4
+ import { structureEditingContext, changeContextFence } from './context-tools.js';
5
+ import { focusContextAction } from './dom/context-keyboard.js';
6
+ import { setMathPresentation } from './presentation.js';
7
+ import { PresentationControls } from './presentation-controls.js';
8
+ import { createMathPreferences } from './preferences.js';
9
+ import { QuickPanel } from './quick-panel.js';
10
+ import { toolbarTemplates } from './dom/toolbar-catalog.js';
11
+ import { insertLatex } from './latex-insertion.js';
12
+ import { LatexPastePanel } from './latex-paste-panel.js';
13
+ import { followMathTheme } from './dom/theme.js';
14
+ import { matrixPointerRange } from './dom/matrix-selection.js';
15
+ import { positionMathMenu, followMathMenuPosition } from './dom/menu-position.js';
16
+ import { fences, fencePairs } from './fences.js';
4
17
  import { parseLatex } from './latex.js';
5
18
  import { createEditorLabels } from './editor-labels.js';
6
19
  import { MathEditorToolbar } from './math-editor-toolbar.js';
7
20
  import { SymbolBrowser } from './symbol-browser.js';
8
21
  import { insertTemplate } from './templates.js';
9
22
  import { translate, mathLocaleDirection, } from './i18n.js';
10
- import { extendKeyboardRange, wrapRange, wrappingKinds, copyRange, deleteRange, fragmentLatex, MATH_CLIPBOARD_TYPE, parseFragment, pasteFragment, resolveRange, } from './range.js';
23
+ import { extendKeyboardRange, collapseKeyboardRange, wrapRange, wrappingKinds, copyRange, deleteRange, fragmentLatex, MATH_CLIPBOARD_TYPE, parseFragment, pasteFragment, resolveRange, } from './range.js';
24
+ import { mathRunSpacing } from './math-spacing.js';
11
25
  import { tokenizeMathText, tokenIndexAt } from './tokens.js';
12
- import { splitLine, joinPreviousLine, moveLineVertical } from './lines.js';
13
- import { insertIdentityMatrix, insertMatrix, activeGrid, MATRIX_MAX_SIZE, matrixEnvironments, moveMatrixVertical, resizeMatrix, setMatrixEnvironment, } from './matrix.js';
26
+ import { splitLine, joinPreviousLine } from './lines.js';
27
+ import { moveVertical } from './vertical-navigation.js';
28
+ import { renderedCaretGeometry } from './dom/caret-geometry.js';
29
+ import { insertIdentityMatrix, insertMatrix, activeGrid, MATRIX_MAX_SIZE, matrixEnvironments, resizeMatrix, setMatrixEnvironment, } from './matrix.js';
30
+ import { MATH_MATRIX_CLIPBOARD_TYPE, parseMatrixFragment, matrixFragment, matrixFromTSV, matrixRangeBetween, resolveMatrixRange, extendMatrixRange, focusMatrixCell, copyMatrixRange, clearMatrixRange, pasteMatrixFragment, transposeMatrix, } from './matrix-range.js';
14
31
  import { createPortal } from 'react-dom';
15
- import { acceptSuggestion, findSuggestions, mathStructures } from './suggestions.js';
32
+ import { acceptSuggestion, findStateSuggestions, mathStructures } from './suggestions.js';
16
33
  import { useId, useImperativeHandle, useLayoutEffect, useRef, useState, } from 'react';
17
- import { gridDeletionTarget, removeGrid, row, documentRows, isLiteralText, isGrid, commit, createHistory, initialState, insertStructure, moveCaret, redo, setText, textNodes, toLatex, undo, unwrapPrevious, } from './model.js';
34
+ import { gridDeletionTarget, removeGrid, row, documentRows, isLiteralText, isGrid, commit, createHistory, initialState, insertStructure, moveCaret, redo, setText, textNodes, toLatex, undo, unwrapPrevious, unwrapEmptySlot, } from './model.js';
18
35
  const structures = mathStructures;
19
36
  /** Native text fields own IME and local text selection; the tree owns structural editing. */
20
- export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline', onCommit, onCancel, showPopovers = true, defaultValue, excludedStructures = [], multiline = true, onChange, onExit, label, locale = 'ko', showLineNumbers = true, toolbar = true, toolbarMaxItems = 8, toolbarEnd, showTokenLegend = true, }) {
37
+ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus = false, enterBehavior = 'newline', onCommit, onCancel, showPopovers = true, defaultValue, excludedStructures = [], multiline = true, onChange, onExit, label, locale = 'ko', showLineNumbers = true, toolbar = true, toolbarMaxItems = 8, contextTools = true, toolbarEnd, showTokenLegend = true, }) {
21
38
  const t = (key, parameters) => translate(locale, key, parameters);
22
39
  const labels = createEditorLabels(locale);
23
40
  const [history, setHistory] = useState(() => createHistory(initialState(defaultValue)));
24
41
  const historyRef = useRef(history);
25
42
  const suggestionMenu = useRef(null);
43
+ const contextFooter = useRef(null);
26
44
  const selectionMenu = useRef(null);
45
+ const [ownPreferences] = useState(createMathPreferences);
46
+ const preferences = suppliedPreferences ?? ownPreferences;
47
+ const [quickOpen, setQuickOpen] = useState(false);
48
+ const [latexOpen, setLatexOpen] = useState(false);
27
49
  const [symbolsOpen, setSymbolsOpen] = useState(false);
28
50
  const [editing, setEditing] = useState(autoFocus);
29
51
  const [range, setRange] = useState();
52
+ const [matrixRange, setMatrixRange] = useState();
30
53
  const [clipboardMessage, setClipboardMessage] = useState('');
31
54
  const surface = useRef(null);
32
55
  const drag = useRef(undefined);
@@ -60,10 +83,16 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
60
83
  const focusNext = useRef(autoFocus);
61
84
  const suggestionId = useId();
62
85
  const state = history.present;
86
+ const structureContext = contextTools && showPopovers && editing && focused && !range && !matrixRange
87
+ ? structureEditingContext(state)
88
+ : undefined;
89
+ const rootContext = structureContext?.root;
90
+ const fenceContext = structureContext?.fence;
63
91
  const matrixContext = activeGrid(state);
92
+ const matrixSelection = matrixRange && resolveMatrixRange(state.document, matrixRange);
64
93
  const active = textNodes(state.document).find((n) => n.id === state.caret.id);
65
94
  const searchCaret = composing ? state.caret.end : state.caret.start;
66
- const matches = findSuggestions(active.text, searchCaret, locale);
95
+ const matches = findStateSuggestions(state, locale, searchCaret);
67
96
  matches.candidates = matches.candidates.filter((candidate) => !candidate.kind || !excludedStructures.includes(candidate.kind));
68
97
  const query = matches.query;
69
98
  const operandMenuKey = useRef('');
@@ -73,6 +102,7 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
73
102
  !isLiteralText(state.document, state.caret.id) &&
74
103
  (composing || state.caret.start === state.caret.end) &&
75
104
  !range &&
105
+ !matrixRange &&
76
106
  focused &&
77
107
  (composing || state.caret.start === state.caret.end) &&
78
108
  dismissed !== queryKey
@@ -85,6 +115,7 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
85
115
  if (changed) {
86
116
  setSelectedGrid(undefined);
87
117
  setRange(undefined);
118
+ setMatrixRange(undefined);
88
119
  }
89
120
  if (focus)
90
121
  setEditing(true);
@@ -98,7 +129,30 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
98
129
  setSelectedGrid(undefined);
99
130
  publish(commit(historyRef.current, next), true);
100
131
  };
132
+ const pasteLatex = (source) => {
133
+ if (composing || matrixRange)
134
+ return {
135
+ ok: false,
136
+ source,
137
+ diagnostics: [
138
+ {
139
+ code: 'unsupported-structure',
140
+ message: 'Finish composition or select one insertion point.',
141
+ start: 0,
142
+ end: source.length,
143
+ },
144
+ ],
145
+ };
146
+ const result = insertLatex(historyRef.current.present, source, range, {
147
+ multiline,
148
+ excludedStructures,
149
+ });
150
+ if (result.ok)
151
+ apply(result.state);
152
+ return result;
153
+ };
101
154
  useImperativeHandle(apiRef, () => ({
155
+ pasteLatex,
102
156
  importLatex(source) {
103
157
  const result = parseLatex(source, { multiline, excludedStructures });
104
158
  if (!result.ok)
@@ -167,12 +221,16 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
167
221
  if (suggestionMenu.current)
168
222
  positionMathMenu(suggestionMenu.current, new DOMRect(x, rect.top, 0, rect.height), 340);
169
223
  };
224
+ const stopTheme = surface.current && suggestionMenu.current
225
+ ? followMathTheme(surface.current.closest('.me-editor'), suggestionMenu.current)
226
+ : undefined;
170
227
  position();
171
- window.addEventListener('resize', position);
172
- window.addEventListener('scroll', position, true);
228
+ const stopPosition = surface.current
229
+ ? followMathMenuPosition(surface.current, position)
230
+ : undefined;
173
231
  return () => {
174
- window.removeEventListener('resize', position);
175
- window.removeEventListener('scroll', position, true);
232
+ stopTheme?.();
233
+ stopPosition?.();
176
234
  };
177
235
  }, [queryKey, candidates.length, active.id, showPopovers]);
178
236
  useLayoutEffect(() => {
@@ -195,6 +253,7 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
195
253
  if (!candidate)
196
254
  return;
197
255
  apply(acceptSuggestion(historyRef.current.present, query, candidate));
256
+ preferences.record(candidate.id);
198
257
  setCandidateIndex(0);
199
258
  setDismissed(`${active.id}:${state.caret.start}:${query}`);
200
259
  };
@@ -257,6 +316,7 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
257
316
  };
258
317
  };
259
318
  const activate = (point) => {
319
+ setMatrixRange(undefined);
260
320
  setRange(undefined);
261
321
  setFocused(true);
262
322
  setSelectedGrid(undefined);
@@ -273,6 +333,19 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
273
333
  },
274
334
  }, true);
275
335
  };
336
+ const selectCells = (selection) => {
337
+ if (!resolveMatrixRange(historyRef.current.present.document, selection))
338
+ return;
339
+ setMatrixRange(selection);
340
+ setRange(undefined);
341
+ setEditing(false);
342
+ setSymbolsOpen(false);
343
+ publish({
344
+ ...historyRef.current,
345
+ present: focusMatrixCell(historyRef.current.present, selection.matrixId, selection.focus),
346
+ });
347
+ surface.current?.focus({ preventScroll: true });
348
+ };
276
349
  // Promote a pointer drag to a model range, including a drag that starts in the active input.
277
350
  const beginPointer = (event) => {
278
351
  if (event.button !== 0 ||
@@ -282,6 +355,17 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
282
355
  const point = pointAt(event.clientX, event.clientY);
283
356
  if (!point)
284
357
  return;
358
+ if (event.shiftKey) {
359
+ const current = historyRef.current.present;
360
+ const rectangle = matrixRangeBetween(current.document, matrixRange
361
+ ? focusMatrixCell(current, matrixRange.matrixId, matrixRange.anchor).caret.id
362
+ : current.caret.id, point.id);
363
+ if (rectangle) {
364
+ event.preventDefault();
365
+ selectCells(rectangle);
366
+ return;
367
+ }
368
+ }
285
369
  const input = event.target instanceof HTMLInputElement;
286
370
  drag.current = { anchor: point, x: event.clientX, y: event.clientY, moving: false, input };
287
371
  if (!input) {
@@ -308,10 +392,20 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
308
392
  event.preventDefault();
309
393
  current.moving = true;
310
394
  setEditing(false);
311
- setRange({ anchor: current.anchor, focus: point });
395
+ const rectangle = current.matrixRange ??
396
+ matrixRangeBetween(historyRef.current.present.document, current.anchor.id, point.id);
397
+ const bounds = rectangle && resolveMatrixRange(historyRef.current.present.document, rectangle);
398
+ if (rectangle && bounds) {
399
+ current.matrixRange = matrixPointerRange(event.currentTarget, rectangle, bounds.matrix.columns, event.clientX, event.clientY);
400
+ selectCells(current.matrixRange);
401
+ }
402
+ else {
403
+ setMatrixRange(undefined);
404
+ setRange({ anchor: current.anchor, focus: point });
405
+ }
312
406
  setSelectedGrid(undefined);
313
407
  event.currentTarget.setPointerCapture(event.pointerId);
314
- event.currentTarget.focus();
408
+ event.currentTarget.focus({ preventScroll: true });
315
409
  };
316
410
  const endPointer = (event) => {
317
411
  const current = drag.current;
@@ -345,17 +439,24 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
345
439
  };
346
440
  // Structured clipboard data preserves editable math; plain LaTeX is the interoperability fallback.
347
441
  const copySelection = (event, cut = false) => {
348
- if (!range || compositionStart.current)
442
+ if (compositionStart.current || (!range && !matrixRange))
349
443
  return;
350
- const fragment = copyRange(historyRef.current.present.document, range);
444
+ const cells = matrixRange && copyMatrixRange(historyRef.current.present.document, matrixRange);
445
+ const fragment = cells
446
+ ? matrixFragment(cells)
447
+ : copyRange(historyRef.current.present.document, range);
351
448
  if (!fragment)
352
449
  return;
353
450
  event.preventDefault();
354
451
  event.stopPropagation();
452
+ if (cells)
453
+ event.clipboardData.setData(MATH_MATRIX_CLIPBOARD_TYPE, JSON.stringify(cells));
355
454
  event.clipboardData.setData(MATH_CLIPBOARD_TYPE, JSON.stringify(fragment));
356
455
  event.clipboardData.setData('text/plain', toLatex(fragmentLatex(fragment)));
357
456
  if (cut)
358
- apply(deleteRange(historyRef.current.present, range));
457
+ apply(matrixRange
458
+ ? clearMatrixRange(historyRef.current.present, matrixRange)
459
+ : deleteRange(historyRef.current.present, range));
359
460
  setClipboardMessage(cut ? 'clipboard.cut' : 'clipboard.copied');
360
461
  };
361
462
  const pasteSelection = (event) => {
@@ -364,6 +465,40 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
364
465
  const source = event.clipboardData.getData(MATH_CLIPBOARD_TYPE);
365
466
  const fragment = source ? parseFragment(source) : undefined;
366
467
  const text = event.clipboardData.getData('text/plain');
468
+ const matrixSource = event.clipboardData.getData(MATH_MATRIX_CLIPBOARD_TYPE);
469
+ const cells = matrixSource ? parseMatrixFragment(matrixSource) : matrixFromTSV(text);
470
+ const current = historyRef.current.present;
471
+ const inMatrix = !!matrixRange || activeGrid(current)?.matrix.type === 'matrix';
472
+ if (matrixSource || (inMatrix && text.includes('\t'))) {
473
+ event.preventDefault();
474
+ event.stopPropagation();
475
+ if (!cells) {
476
+ setClipboardMessage('matrix.pasteRejected');
477
+ return;
478
+ }
479
+ const next = inMatrix
480
+ ? pasteMatrixFragment(current, cells, matrixRange)
481
+ : pasteFragment(current, matrixFragment(cells), range);
482
+ if (next === current)
483
+ setClipboardMessage('matrix.pasteRejected');
484
+ else {
485
+ apply(next);
486
+ setClipboardMessage('clipboard.pasted');
487
+ }
488
+ return;
489
+ }
490
+ if (matrixRange && !fragment && !text.includes('\n') && !text.includes('\r')) {
491
+ event.preventDefault();
492
+ event.stopPropagation();
493
+ apply(clearMatrixRange(current, matrixRange, text));
494
+ return;
495
+ }
496
+ if (matrixRange) {
497
+ event.preventDefault();
498
+ event.stopPropagation();
499
+ setClipboardMessage('matrix.pasteRejected');
500
+ return;
501
+ }
367
502
  if (!fragment && !text)
368
503
  return;
369
504
  event.preventDefault();
@@ -383,6 +518,43 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
383
518
  apply(next);
384
519
  setClipboardMessage('clipboard.pasted');
385
520
  };
521
+ const matrixSelectionKey = (event) => {
522
+ if (!matrixRange || compositionStart.current || event.nativeEvent.isComposing)
523
+ return false;
524
+ const current = historyRef.current.present;
525
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'a') {
526
+ event.preventDefault();
527
+ event.stopPropagation();
528
+ const matrix = resolveMatrixRange(current.document, matrixRange)?.matrix;
529
+ if (matrix)
530
+ selectCells({ matrixId: matrix.id, anchor: 0, focus: matrix.slots.length - 1 });
531
+ return true;
532
+ }
533
+ if (event.metaKey || event.ctrlKey || event.altKey)
534
+ return false;
535
+ const arrow = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.key);
536
+ if (arrow || ['Escape', 'Enter', 'F2'].includes(event.key)) {
537
+ event.preventDefault();
538
+ event.stopPropagation();
539
+ const next = arrow
540
+ ? extendMatrixRange(current.document, matrixRange, event.key)
541
+ : matrixRange;
542
+ if (arrow && event.shiftKey)
543
+ selectCells(next);
544
+ else {
545
+ const caret = focusMatrixCell(current, next.matrixId, next.focus).caret;
546
+ activate({ id: caret.id, offset: caret.start });
547
+ }
548
+ return true;
549
+ }
550
+ if (['Delete', 'Backspace'].includes(event.key) || Array.from(event.key).length === 1) {
551
+ event.preventDefault();
552
+ event.stopPropagation();
553
+ apply(clearMatrixRange(current, matrixRange, ['Delete', 'Backspace'].includes(event.key) ? '' : event.key));
554
+ return true;
555
+ }
556
+ return false;
557
+ };
386
558
  const keyboardSelect = (event) => {
387
559
  if (!event.shiftKey ||
388
560
  event.altKey ||
@@ -399,7 +571,7 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
399
571
  const selectionKey = (event) => {
400
572
  if (event.target instanceof HTMLInputElement || compositionStart.current)
401
573
  return;
402
- if (keyboardSelect(event))
574
+ if (matrixSelectionKey(event) || keyboardSelect(event))
403
575
  return;
404
576
  const current = historyRef.current.present, nodes = textNodes(current.document);
405
577
  if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'a') {
@@ -415,9 +587,9 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
415
587
  publish((event.shiftKey ? redo : undo)(historyRef.current), false);
416
588
  return;
417
589
  }
418
- if (event.metaKey || event.ctrlKey || event.altKey)
590
+ if (event.metaKey || event.ctrlKey)
419
591
  return;
420
- if (range && selectionChoices.length && ['ArrowDown', 'ArrowUp', 'Enter'].includes(event.key)) {
592
+ if (range && selectionChoices.length && ['Enter', 'ArrowDown', 'ArrowUp'].includes(event.key)) {
421
593
  event.preventDefault();
422
594
  if (event.key === 'Enter') {
423
595
  if (canWrap)
@@ -428,6 +600,14 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
428
600
  selectionChoices.length);
429
601
  return;
430
602
  }
603
+ const point = !event.altKey && collapseKeyboardRange(current.document, range, event.key);
604
+ if (point) {
605
+ event.preventDefault();
606
+ activate(point);
607
+ return;
608
+ }
609
+ if (event.altKey)
610
+ return;
431
611
  // A model selection must behave like a text selection when typing resumes.
432
612
  if (range && !event.nativeEvent.isComposing && Array.from(event.key).length === 1) {
433
613
  event.preventDefault();
@@ -441,6 +621,7 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
441
621
  }
442
622
  if (event.key === 'Escape') {
443
623
  setRange(undefined);
624
+ setMatrixRange(undefined);
444
625
  return;
445
626
  }
446
627
  if (event.key === 'Enter' || event.key === 'F2') {
@@ -448,13 +629,15 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
448
629
  activate(range?.focus ?? { id: current.caret.id, offset: current.caret.start });
449
630
  }
450
631
  };
451
- const selectedRange = range ??
452
- (state.caret.start !== state.caret.end
453
- ? {
454
- anchor: { id: state.caret.id, offset: state.caret.start },
455
- focus: { id: state.caret.id, offset: state.caret.end },
456
- }
457
- : undefined);
632
+ const selectedRange = matrixRange
633
+ ? undefined
634
+ : range ??
635
+ (state.caret.start !== state.caret.end
636
+ ? {
637
+ anchor: { id: state.caret.id, offset: state.caret.start },
638
+ focus: { id: state.caret.id, offset: state.caret.end },
639
+ }
640
+ : undefined);
458
641
  const selectionFragment = selectedRange ? copyRange(state.document, selectedRange) : undefined;
459
642
  const canWrap = selectionFragment?.rows.length === 1 &&
460
643
  !isLiteralText(state.document, selectedRange.anchor.id);
@@ -504,12 +687,16 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
504
687
  if (rect && selectionMenu.current)
505
688
  positionMathMenu(selectionMenu.current, rect, 340);
506
689
  };
690
+ const stopTheme = surface.current && selectionMenu.current
691
+ ? followMathTheme(surface.current.closest('.me-editor'), selectionMenu.current)
692
+ : undefined;
507
693
  position();
508
- window.addEventListener('scroll', position, true);
509
- window.addEventListener('resize', position);
694
+ const stopPosition = surface.current
695
+ ? followMathMenuPosition(surface.current, position)
696
+ : undefined;
510
697
  return () => {
511
- window.removeEventListener('scroll', position, true);
512
- window.removeEventListener('resize', position);
698
+ stopPosition?.();
699
+ stopTheme?.();
513
700
  };
514
701
  }, [range, state.document, state.caret, showPopovers]);
515
702
  useLayoutEffect(() => {
@@ -524,7 +711,7 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
524
711
  list.scrollTop += item.bottom - box.bottom;
525
712
  }, [selectionIndex]);
526
713
  const insertTool = (kind) => {
527
- if (excludedStructures.includes(kind))
714
+ if (excludedStructures.includes(kind) || matrixRange)
528
715
  return;
529
716
  const current = historyRef.current.present;
530
717
  const selection = range ??
@@ -543,300 +730,370 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
543
730
  apply(insertStructure(historyRef.current.present, kind, ['fraction', 'binomial', 'superscript', 'subscript'].includes(kind)));
544
731
  };
545
732
  // Nested slots share the same caret model. Only the active lexical token mounts an input.
546
- const renderRow = (r, name, role = 'expression') => (_jsx("span", { className: "me-row", "data-math-role": role, children: r.children.map((node) => {
547
- if (isGrid(node))
548
- return (_jsxs("span", { className: "me-matrix", "data-selected": selectedGrid === node.id || undefined, "data-structure": node.type, "data-environment": node.type === 'matrix'
549
- ? node.environment
550
- : node.type === 'cases'
551
- ? 'Bmatrix'
552
- : 'matrix', children: [_jsx("span", { className: "me-matrix-delimiter me-matrix-left", "aria-hidden": "true" }), _jsx("span", { className: "me-matrix-grid", style: { gridTemplateColumns: `repeat(${node.columns}, max-content)` }, children: node.slots.map((cell, index) => (_jsx("span", { className: "me-matrix-cell", children: renderRow(cell, labels.grid(name, node.type, index, node.columns), 'group') }, cell.id))) }), _jsx("span", { className: "me-matrix-delimiter me-matrix-right", "aria-hidden": "true" })] }, node.id));
553
- if (node.type === 'limit' || node.type === 'limsup' || node.type === 'liminf')
554
- return (_jsxs("span", { className: "me-structure me-limit-operator", "data-structure": node.type, "data-math-style": node.mathStyle, "data-limit-placement": node.limits === false ? 'nolimits' : node.limits ? 'limits' : 'auto', children: [_jsxs("span", { className: "me-limits", children: [_jsx("span", { className: "me-limit-glyph", "aria-hidden": "true", children: node.type === 'limit' ? 'lim' : node.type === 'limsup' ? 'lim sup' : 'lim inf' }), _jsx("span", { className: "me-limit me-slot", children: renderRow(node.slots[0], labels.slot(name, node.type, 0), 'lower') })] }), _jsx("span", { className: "me-slot", children: renderRow(node.slots[1], labels.slot(name, node.type, 1)) })] }, node.id));
555
- if (node.type !== 'text' &&
556
- [
557
- 'sum',
558
- 'product',
559
- 'integral',
560
- 'doubleIntegral',
561
- 'tripleIntegral',
562
- 'contourIntegral',
563
- ].includes(node.type))
564
- return (_jsxs("span", { className: "me-structure me-large-operator", "data-limit-placement": node.limits === false ? 'nolimits' : node.limits ? 'limits' : 'auto', "data-structure": node.type, "data-math-style": node.mathStyle, children: [_jsxs("span", { className: "me-limits", children: [_jsx("span", { className: "me-limit", children: renderRow(node.slots[1], labels.slot(name, node.type, 1), 'upper') }), _jsx("span", { className: "me-operator-glyph", "aria-hidden": "true", children: {
565
- sum: '',
566
- product: '',
567
- integral: '',
568
- doubleIntegral: '∬',
569
- tripleIntegral: '',
570
- contourIntegral: '',
571
- }[node.type] }), _jsx("span", { className: "me-limit", children: renderRow(node.slots[0], labels.slot(name, node.type, 0), 'lower') })] }), renderRow(node.slots[2], labels.slot(name, node.type, 2))] }, node.id));
572
- if (node.type !== 'text')
573
- return (_jsxs("span", { className: `me-structure me-${node.type}`, "data-structure": node.type, "data-math-style": node.mathStyle, children: [!!fences(node) && (_jsx("span", { className: "me-delimiter", "data-fence": fences(node)[0], "aria-hidden": "true", children: fences(node)[0] })), ['root', 'indexedRoot'].includes(node.type) && (_jsx("span", { "aria-hidden": "true", className: "me-radical", children: "\u221A" })), node.slots.map((slot, index) => (_jsx("span", { className: "me-slot", children: renderRow(slot, labels.slot(name, node.type, index), node.type === 'fraction'
574
- ? index === 0
575
- ? 'numerator'
576
- : 'denominator'
577
- : node.type === 'superscript'
733
+ const layout = mathLayout(state.document);
734
+ const renderRow = (r, name, role = 'expression') => {
735
+ const rowSpacing = mathRunSpacing(r);
736
+ return (_jsx("span", { className: "me-row", "data-layout-style": layout.rows.get(r.id), "data-math-role": role, children: r.children.map((node) => {
737
+ if (isGrid(node))
738
+ return (_jsxs("span", { className: "me-matrix", "data-selected": selectedGrid === node.id || undefined, "data-structure": node.type, "data-matrix-id": node.id, "data-environment": node.type === 'matrix'
739
+ ? node.environment
740
+ : node.type === 'cases'
741
+ ? 'Bmatrix'
742
+ : 'matrix', children: [_jsx("span", { className: "me-matrix-delimiter me-matrix-left", "aria-hidden": "true" }), _jsx("span", { className: "me-matrix-grid", style: { gridTemplateColumns: `repeat(${node.columns}, max-content)` }, children: node.slots.map((cell, index) => (_jsx("span", { className: "me-matrix-cell", "data-cell-index": index, "data-cell-selected": (matrixSelection?.matrix.id === node.id &&
743
+ matrixSelection.indices.includes(index)) ||
744
+ undefined, children: renderRow(cell, labels.grid(name, node.type, index, node.columns), 'group') }, cell.id))) }), _jsx("span", { className: "me-matrix-delimiter me-matrix-right", "aria-hidden": "true" })] }, node.id));
745
+ if (node.type === 'limit' || node.type === 'limsup' || node.type === 'liminf')
746
+ return (_jsxs("span", { className: "me-structure me-limit-operator", "data-operator-style": layout.operators.get(node.id)?.style, "data-layout-limits": layout.operators.get(node.id)?.placement, "data-structure": node.type, "data-math-style": node.mathStyle, "data-limit-placement": node.limits === false ? 'nolimits' : node.limits ? 'limits' : 'auto', children: [_jsxs("span", { className: "me-limits", children: [_jsx("span", { className: "me-limit-glyph", "aria-hidden": "true", children: node.type === 'limit' ? 'lim' : node.type === 'limsup' ? 'lim sup' : 'lim inf' }), _jsx("span", { className: "me-limit me-slot", children: renderRow(node.slots[0], labels.slot(name, node.type, 0), 'lower') })] }), _jsx("span", { className: "me-slot", children: renderRow(node.slots[1], labels.slot(name, node.type, 1)) })] }, node.id));
747
+ if (node.type !== 'text' &&
748
+ [
749
+ 'sum',
750
+ 'product',
751
+ 'integral',
752
+ 'doubleIntegral',
753
+ 'tripleIntegral',
754
+ 'contourIntegral',
755
+ ].includes(node.type))
756
+ return (_jsxs("span", { className: "me-structure me-large-operator", "data-operator-style": layout.operators.get(node.id)?.style, "data-layout-limits": layout.operators.get(node.id)?.placement, "data-limit-placement": node.limits === false ? 'nolimits' : node.limits ? 'limits' : 'auto', "data-structure": node.type, "data-math-style": node.mathStyle, children: [_jsxs("span", { className: "me-limits", children: [_jsx("span", { className: "me-limit", children: renderRow(node.slots[1], labels.slot(name, node.type, 1), 'upper') }), _jsx("span", { className: "me-operator-glyph", "aria-hidden": "true", children: {
757
+ sum: '',
758
+ product: '',
759
+ integral: '',
760
+ doubleIntegral: '',
761
+ tripleIntegral: '∭',
762
+ contourIntegral: '',
763
+ }[node.type] }), _jsx("span", { className: "me-limit", children: renderRow(node.slots[0], labels.slot(name, node.type, 0), 'lower') })] }), renderRow(node.slots[2], labels.slot(name, node.type, 2))] }, node.id));
764
+ if (node.type !== 'text')
765
+ return (_jsxs("span", { className: `me-structure me-${node.type}`, "data-structure": node.type, "data-math-style": node.mathStyle, "data-math-spacing": rowSpacing.has(node.id) ? 'true' : undefined, style: rowSpacing.has(node.id)
766
+ ? {
767
+ '--me-space-before': rowSpacing.get(node.id)?.before ?? 0,
768
+ '--me-space-after': rowSpacing.get(node.id)?.after ?? 0,
769
+ '--me-tight-before': rowSpacing.get(node.id)?.tightBefore ?? 0,
770
+ '--me-tight-after': rowSpacing.get(node.id)?.tightAfter ?? 0,
771
+ }
772
+ : undefined, children: [!!fences(node) && (_jsx("span", { className: "me-delimiter", "data-fence": fences(node)[0], "aria-hidden": "true", children: fences(node)[0] })), ['root', 'indexedRoot'].includes(node.type) && (_jsx("span", { "aria-hidden": "true", className: "me-radical", children: "\u221A" })), node.slots.map((slot, index) => (_jsx("span", { className: "me-slot", children: renderRow(slot, labels.slot(name, node.type, index), node.type === 'fraction'
578
773
  ? index === 0
579
- ? 'base'
580
- : 'exponent'
581
- : node.type === 'subscript'
774
+ ? 'numerator'
775
+ : 'denominator'
776
+ : node.type === 'superscript'
582
777
  ? index === 0
583
778
  ? 'base'
584
- : 'index'
585
- : node.type === 'root'
586
- ? 'radicand'
587
- : 'group') }, slot.id))), !!fences(node) && (_jsx("span", { className: "me-delimiter", "data-fence": fences(node)[1], "aria-hidden": "true", children: fences(node)[1] }))] }, node.id));
588
- const parts = tokensFor(node);
589
- return parts.map((token, tokenIndex) => {
590
- const key = `${node.id}:${tokenIndex}`;
591
- const selected = selectedText(node.id, token.start, token.end);
592
- const isActive = editing &&
593
- !range &&
594
- state.caret.id === node.id &&
595
- tokenIndexAt(parts, state.caret.start, state.caret.affinity) === tokenIndex;
596
- const caretFrom = (input) => ({
597
- id: node.id,
598
- start: token.start + (input.selectionStart ?? 0),
599
- end: token.start + (input.selectionEnd ?? 0),
600
- affinity: input.selectionStart === 0 ? 'forward' : 'backward',
601
- });
602
- const replaceToken = (input) => {
603
- const current = historyRef.current.present;
604
- const run = textNodes(current.document).find((n) => n.id === node.id);
605
- return setText({ ...current, caret: { ...current.caret, id: node.id } }, run.text.slice(0, token.start) + input.value + run.text.slice(token.end), token.start + (input.selectionStart ?? 0), token.start + (input.selectionEnd ?? 0));
606
- };
607
- return (_jsxs("span", { className: `me-run${!token.text ? ' me-empty' : ''}${!node.text && r.children.length > 1 ? ' me-empty-boundary' : ''}`, "data-token-kind": token.kind, "data-run-id": node.id, "data-token-start": token.start, "data-token-end": token.end, "data-field-name": name, "data-value": token.text, "data-range-selected": !!selected || undefined, children: [_jsx("span", { className: "me-measure", "aria-hidden": "true", children: token.text || (r.children.length === 1 ? '□' : '') }), isActive ? (_jsx("input", { ref: (element) => {
608
- if (element)
609
- inputs.current.set(key, element);
610
- else
611
- inputs.current.delete(key);
612
- }, "aria-label": name, "aria-description": t({
613
- variable: 'token.variable',
614
- constant: 'token.constant',
615
- symbol: 'token.symbol',
616
- space: 'token.space',
617
- empty: 'token.empty',
618
- draft: 'token.composing',
619
- }[token.kind]), "data-token-kind": token.kind, "data-run-id": node.id, "data-token-start": token.start, "aria-autocomplete": "list", "aria-controls": isActive && candidates.length ? suggestionId : undefined, "aria-expanded": isActive && candidates.length > 0, role: "combobox", "aria-activedescendant": isActive && !composing && candidates.length
620
- ? `${suggestionId}-${selectedIndex}`
621
- : undefined, className: "me-input", value: token.text, placeholder: r.children.length === 1 ? '□' : '', autoComplete: "off", spellCheck: false, onPointerDown: () => setSelectedGrid(undefined), onFocus: (event) => {
622
- setFocused(true);
623
- select(caretFrom(event.currentTarget));
624
- }, onSelect: (event) => select(caretFrom(event.currentTarget)), onCompositionStart: () => {
625
- compositionStart.current = historyRef.current;
626
- compositionToken.current = {
627
- id: node.id,
628
- index: tokenIndex,
629
- length: node.text.length,
630
- parts,
631
- };
632
- setComposing(true);
633
- }, onCompositionEnd: (event) => {
634
- const next = replaceToken(event.currentTarget);
635
- const base = compositionStart.current;
636
- compositionStart.current = null;
637
- compositionToken.current = null;
638
- setComposing(false);
639
- publish(commit(base ?? historyRef.current, next), true);
640
- }, onChange: (event) => {
641
- setSelectedGrid(undefined);
642
- const current = historyRef.current, next = replaceToken(event.currentTarget);
643
- publish(compositionStart.current
644
- ? { ...current, present: next }
645
- : commit(current, next), !compositionStart.current, !compositionStart.current);
646
- setCandidateIndex(0);
647
- setDismissed('');
648
- }, onKeyDown: (event) => {
649
- event.stopPropagation();
650
- if (event.nativeEvent.isComposing ||
651
- compositionStart.current ||
652
- event.keyCode === 229)
653
- return;
654
- if (keyboardSelect(event))
655
- return;
656
- const current = historyRef.current.present;
657
- if (selectedGrid) {
779
+ : 'exponent'
780
+ : node.type === 'subscript'
781
+ ? index === 0
782
+ ? 'base'
783
+ : 'index'
784
+ : node.type === 'root'
785
+ ? 'radicand'
786
+ : 'group') }, slot.id))), !!fences(node) && (_jsx("span", { className: "me-delimiter", "data-fence": fences(node)[1], "aria-hidden": "true", children: fences(node)[1] }))] }, node.id));
787
+ const parts = tokensFor(node);
788
+ const spacing = isLiteralText(state.document, node.id) ? undefined : rowSpacing;
789
+ return parts.map((token, tokenIndex) => {
790
+ const key = `${node.id}:${tokenIndex}`;
791
+ const selected = selectedText(node.id, token.start, token.end);
792
+ const isActive = editing &&
793
+ !range &&
794
+ !matrixRange &&
795
+ state.caret.id === node.id &&
796
+ tokenIndexAt(parts, state.caret.start, state.caret.affinity) === tokenIndex;
797
+ const caretFrom = (input) => ({
798
+ id: node.id,
799
+ start: token.start + (input.selectionStart ?? 0),
800
+ end: token.start + (input.selectionEnd ?? 0),
801
+ affinity: input.selectionStart === 0 ? 'forward' : 'backward',
802
+ });
803
+ const replaceToken = (input) => {
804
+ const current = historyRef.current.present;
805
+ const run = textNodes(current.document).find((n) => n.id === node.id);
806
+ return setText({ ...current, caret: { ...current.caret, id: node.id } }, run.text.slice(0, token.start) + input.value + run.text.slice(token.end), token.start + (input.selectionStart ?? 0), token.start + (input.selectionEnd ?? 0));
807
+ };
808
+ return (_jsxs("span", { className: `me-run${!token.text ? ' me-empty' : ''}${!node.text && r.children.length > 1 ? ' me-empty-boundary' : ''}`, "data-token-kind": token.kind, "data-math-spacing": spacing ? 'true' : undefined, style: spacing
809
+ ? {
810
+ '--me-space-before': spacing.get(`${node.id}:${token.start}`)?.before ?? 0,
811
+ '--me-space-after': spacing.get(`${node.id}:${token.start}`)?.after ?? 0,
812
+ '--me-tight-before': spacing.get(`${node.id}:${token.start}`)?.tightBefore ?? 0,
813
+ '--me-tight-after': spacing.get(`${node.id}:${token.start}`)?.tightAfter ?? 0,
814
+ }
815
+ : undefined, "data-run-id": node.id, "data-token-start": token.start, "data-token-end": token.end, "data-field-name": name, "data-value": token.text, "data-range-selected": !!selected || undefined, children: [_jsx("span", { className: "me-measure", "aria-hidden": "true", children: token.text || (r.children.length === 1 ? '□' : '') }), isActive ? (_jsx("input", { ref: (element) => {
816
+ if (element)
817
+ inputs.current.set(key, element);
818
+ else
819
+ inputs.current.delete(key);
820
+ }, "aria-label": name, "aria-description": t({
821
+ variable: 'token.variable',
822
+ constant: 'token.constant',
823
+ symbol: 'token.symbol',
824
+ space: 'token.space',
825
+ empty: 'token.empty',
826
+ draft: 'token.composing',
827
+ }[token.kind]), "data-token-kind": token.kind, "data-run-id": node.id, "data-token-start": token.start, "aria-autocomplete": "list", "aria-controls": isActive && candidates.length ? suggestionId : undefined, "aria-expanded": isActive && candidates.length > 0, role: "combobox", "aria-activedescendant": isActive && !composing && candidates.length
828
+ ? `${suggestionId}-${selectedIndex}`
829
+ : undefined, className: "me-input", value: token.text, placeholder: r.children.length === 1 ? '□' : '', autoComplete: "off", spellCheck: false, onPointerDown: () => setSelectedGrid(undefined), onFocus: (event) => {
830
+ setFocused(true);
831
+ select(caretFrom(event.currentTarget));
832
+ }, onSelect: (event) => select(caretFrom(event.currentTarget)), onCompositionStart: () => {
833
+ compositionStart.current = historyRef.current;
834
+ compositionToken.current = {
835
+ id: node.id,
836
+ index: tokenIndex,
837
+ length: node.text.length,
838
+ parts,
839
+ };
840
+ setComposing(true);
841
+ }, onCompositionEnd: (event) => {
842
+ const next = replaceToken(event.currentTarget);
843
+ const base = compositionStart.current;
844
+ compositionStart.current = null;
845
+ compositionToken.current = null;
846
+ setComposing(false);
847
+ publish(commit(base ?? historyRef.current, next), true);
848
+ }, onChange: (event) => {
849
+ setSelectedGrid(undefined);
850
+ const current = historyRef.current, next = replaceToken(event.currentTarget);
851
+ publish(compositionStart.current
852
+ ? { ...current, present: next }
853
+ : commit(current, next), !compositionStart.current, !compositionStart.current);
854
+ setCandidateIndex(0);
855
+ setDismissed('');
856
+ }, onKeyDown: (event) => {
857
+ event.stopPropagation();
858
+ if (event.nativeEvent.isComposing ||
859
+ compositionStart.current ||
860
+ event.keyCode === 229)
861
+ return;
862
+ if (matrixSelectionKey(event) || keyboardSelect(event))
863
+ return;
864
+ const current = historyRef.current.present;
865
+ if (!event.shiftKey &&
866
+ !event.altKey &&
867
+ !event.ctrlKey &&
868
+ !event.metaKey &&
869
+ ['Backspace', 'Delete'].includes(event.key)) {
870
+ const unwrapped = unwrapEmptySlot(current);
871
+ if (unwrapped !== current) {
872
+ event.preventDefault();
873
+ apply(unwrapped);
874
+ return;
875
+ }
876
+ }
877
+ if (selectedGrid) {
878
+ if (!event.metaKey &&
879
+ !event.ctrlKey &&
880
+ !event.altKey &&
881
+ !event.shiftKey &&
882
+ ['Backspace', 'Delete'].includes(event.key)) {
883
+ event.preventDefault();
884
+ if (!event.repeat)
885
+ apply(removeGrid(current, selectedGrid));
886
+ return;
887
+ }
888
+ setSelectedGrid(undefined);
889
+ if (event.key === 'Escape') {
890
+ event.preventDefault();
891
+ return;
892
+ }
893
+ }
658
894
  if (!event.metaKey &&
659
895
  !event.ctrlKey &&
660
896
  !event.altKey &&
661
897
  !event.shiftKey &&
662
898
  ['Backspace', 'Delete'].includes(event.key)) {
899
+ const grid = gridDeletionTarget(current, event.key === 'Backspace' ? -1 : 1);
900
+ if (grid) {
901
+ event.preventDefault();
902
+ const empty = grid.slots.every((slot) => slot.children.every((child) => child.type === 'text' && !child.text));
903
+ if (empty)
904
+ apply(removeGrid(current, grid.id));
905
+ else
906
+ setSelectedGrid(grid.id);
907
+ return;
908
+ }
909
+ }
910
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'z') {
663
911
  event.preventDefault();
664
- if (!event.repeat)
665
- apply(removeGrid(current, selectedGrid));
912
+ publish((event.shiftKey ? redo : undo)(historyRef.current), true);
666
913
  return;
667
914
  }
668
- setSelectedGrid(undefined);
669
- if (event.key === 'Escape') {
915
+ if (event.altKey &&
916
+ event.shiftKey &&
917
+ !event.ctrlKey &&
918
+ !event.metaKey &&
919
+ activeGrid(current) &&
920
+ ['ArrowUp', 'ArrowLeft'].includes(event.key)) {
670
921
  event.preventDefault();
922
+ apply(resizeMatrix(current, event.key === 'ArrowUp' ? 'row' : 'column', 'delete'));
671
923
  return;
672
924
  }
673
- }
674
- if (!event.metaKey &&
675
- !event.ctrlKey &&
676
- !event.altKey &&
677
- !event.shiftKey &&
678
- ['Backspace', 'Delete'].includes(event.key)) {
679
- const grid = gridDeletionTarget(current, event.key === 'Backspace' ? -1 : 1);
680
- if (grid) {
925
+ if (event.altKey &&
926
+ !event.shiftKey &&
927
+ !event.ctrlKey &&
928
+ !event.metaKey &&
929
+ event.key === 'ArrowDown') {
681
930
  event.preventDefault();
682
- const empty = grid.slots.every((slot) => slot.children.every((child) => child.type === 'text' && !child.text));
683
- if (empty)
684
- apply(removeGrid(current, grid.id));
685
- else
686
- setSelectedGrid(grid.id);
931
+ setDismissed('');
932
+ setCandidateIndex(Math.max(0, matches.candidates.findIndex((item) => item.transformFenceId || item.transformRootId)));
933
+ operandMenuKey.current = queryKey;
687
934
  return;
688
935
  }
689
- }
690
- if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'z') {
691
- event.preventDefault();
692
- publish((event.shiftKey ? redo : undo)(historyRef.current), true);
693
- return;
694
- }
695
- if (event.altKey &&
696
- event.shiftKey &&
697
- !event.ctrlKey &&
698
- !event.metaKey &&
699
- activeGrid(current) &&
700
- ['ArrowUp', 'ArrowLeft'].includes(event.key)) {
701
- event.preventDefault();
702
- apply(resizeMatrix(current, event.key === 'ArrowUp' ? 'row' : 'column', 'delete'));
703
- return;
704
- }
705
- if (event.ctrlKey || event.metaKey || event.altKey)
706
- return;
707
- if (activeGrid(current) &&
708
- event.shiftKey &&
709
- ['Enter', ' '].includes(event.key)) {
710
- event.preventDefault();
711
- apply(resizeMatrix(current, event.key === 'Enter' ? 'row' : 'column', 'insert'));
712
- return;
713
- }
714
- if (selectedRange &&
715
- !event.metaKey &&
716
- !event.ctrlKey &&
717
- !event.altKey &&
718
- ['ArrowDown', 'ArrowUp', 'Enter'].includes(event.key)) {
719
- event.preventDefault();
720
- if (event.key === 'Enter') {
721
- if (canWrap)
722
- insertTool(selectionChoices[selectionIndex % selectionChoices.length].kind);
723
- }
724
- else
725
- setSelectionIndex((selectionIndex +
726
- (event.key === 'ArrowDown' ? 1 : selectionChoices.length - 1)) %
727
- selectionChoices.length);
728
- return;
729
- }
730
- if (candidates.length && ['ArrowDown', 'ArrowUp'].includes(event.key)) {
731
- event.preventDefault();
732
- const initialOperandChoice = candidates[0]?.wrapOperand && operandMenuKey.current !== queryKey;
733
- operandMenuKey.current = queryKey;
734
- setCandidateIndex(initialOperandChoice
735
- ? event.key === 'ArrowDown'
736
- ? 0
737
- : candidates.length - 1
738
- : (selectedIndex +
739
- (event.key === 'ArrowDown' ? 1 : candidates.length - 1)) %
740
- candidates.length);
741
- return;
742
- }
743
- if (event.key === 'Enter' &&
744
- candidates.length &&
745
- (!candidates[0].wrapOperand || operandMenuKey.current === queryKey)) {
746
- event.preventDefault();
747
- choose(selectedIndex);
748
- return;
749
- }
750
- if (event.key === 'Enter') {
751
- event.preventDefault();
752
- if (enterBehavior === 'commit' && !event.shiftKey) {
753
- onCommit?.();
936
+ if (event.ctrlKey || event.metaKey || event.altKey)
937
+ return;
938
+ if (activeGrid(current) &&
939
+ event.shiftKey &&
940
+ ['Enter', ' '].includes(event.key)) {
941
+ event.preventDefault();
942
+ apply(resizeMatrix(current, event.key === 'Enter' ? 'row' : 'column', 'insert'));
754
943
  return;
755
944
  }
756
- if (activeGrid(current)) {
757
- if (activeGrid(current).matrix.type !== 'matrix')
758
- apply(resizeMatrix(current, 'row', 'insert'));
945
+ if (selectedRange &&
946
+ !event.metaKey &&
947
+ !event.ctrlKey &&
948
+ !event.altKey &&
949
+ ['ArrowDown', 'ArrowUp', 'Enter'].includes(event.key)) {
950
+ event.preventDefault();
951
+ if (event.key === 'Enter') {
952
+ if (canWrap)
953
+ insertTool(selectionChoices[selectionIndex % selectionChoices.length].kind);
954
+ }
955
+ else
956
+ setSelectionIndex((selectionIndex +
957
+ (event.key === 'ArrowDown' ? 1 : selectionChoices.length - 1)) %
958
+ selectionChoices.length);
959
+ return;
759
960
  }
760
- else if (multiline)
761
- apply(splitLine(current));
762
- return;
763
- }
764
- if (event.key === 'Escape') {
765
- event.preventDefault();
766
- if (candidates.length)
767
- setDismissed(queryKey);
768
- else if (onCancel)
769
- onCancel();
770
- else {
771
- setEditing(false);
772
- surface.current?.focus();
961
+ if (candidates.length && ['ArrowDown', 'ArrowUp'].includes(event.key)) {
962
+ event.preventDefault();
963
+ const initialOperandChoice = (candidates[0]?.wrapOperand ||
964
+ candidates[0]?.transformRootId ||
965
+ candidates[0]?.transformFenceId) &&
966
+ operandMenuKey.current !== queryKey;
967
+ operandMenuKey.current = queryKey;
968
+ setCandidateIndex(initialOperandChoice
969
+ ? event.key === 'ArrowDown'
970
+ ? 0
971
+ : candidates.length - 1
972
+ : (selectedIndex +
973
+ (event.key === 'ArrowDown' ? 1 : candidates.length - 1)) %
974
+ candidates.length);
975
+ return;
773
976
  }
774
- return;
775
- }
776
- if (!event.shiftKey &&
777
- current.caret.start === current.caret.end &&
778
- activeGrid(current) &&
779
- ['ArrowUp', 'ArrowDown'].includes(event.key)) {
780
- event.preventDefault();
781
- publish({
782
- ...historyRef.current,
783
- present: moveMatrixVertical(current, event.key === 'ArrowUp' ? -1 : 1),
784
- }, true);
785
- return;
786
- }
787
- if (!event.shiftKey &&
788
- current.caret.start === current.caret.end &&
789
- ['ArrowUp', 'ArrowDown'].includes(event.key)) {
790
- const next = moveLineVertical(current, event.key === 'ArrowUp' ? -1 : 1);
791
- if (next !== current) {
977
+ if (event.key === 'Enter' &&
978
+ candidates.length &&
979
+ ((!candidates[0].wrapOperand &&
980
+ !candidates[0].transformRootId &&
981
+ !candidates[0].transformFenceId) ||
982
+ operandMenuKey.current === queryKey)) {
792
983
  event.preventDefault();
793
- publish({ ...historyRef.current, present: next }, true);
984
+ choose(selectedIndex);
794
985
  return;
795
986
  }
796
- }
797
- if (event.key === 'Tab') {
798
- if (navigate(event.shiftKey ? -1 : 1))
987
+ if (event.key === 'Enter') {
799
988
  event.preventDefault();
800
- return;
801
- }
802
- if (event.shiftKey || current.caret.start !== current.caret.end)
803
- return;
804
- if (event.key === 'ArrowLeft' && current.caret.start === token.start) {
805
- if (navigate(-1))
989
+ if (enterBehavior === 'commit' && !event.shiftKey) {
990
+ onCommit?.();
991
+ return;
992
+ }
993
+ if (activeGrid(current)) {
994
+ if (activeGrid(current).matrix.type !== 'matrix')
995
+ apply(resizeMatrix(current, 'row', 'insert'));
996
+ }
997
+ else if (multiline)
998
+ apply(splitLine(current));
999
+ return;
1000
+ }
1001
+ if (event.key === 'Escape') {
806
1002
  event.preventDefault();
807
- }
808
- if (event.key === 'ArrowRight' && current.caret.end === token.end) {
809
- if (navigate(1))
1003
+ if (candidates.length)
1004
+ setDismissed(queryKey);
1005
+ else if (onCancel)
1006
+ onCancel();
1007
+ else {
1008
+ setEditing(false);
1009
+ surface.current?.focus();
1010
+ }
1011
+ return;
1012
+ }
1013
+ if (!event.shiftKey &&
1014
+ current.caret.start === current.caret.end &&
1015
+ ['ArrowUp', 'ArrowDown'].includes(event.key)) {
1016
+ const next = moveVertical(current, event.key === 'ArrowUp' ? -1 : 1, surface.current ? renderedCaretGeometry(surface.current) : undefined);
1017
+ if (next !== current) {
1018
+ event.preventDefault();
1019
+ publish({ ...historyRef.current, present: next }, true);
1020
+ return;
1021
+ }
1022
+ }
1023
+ if (event.key === 'Tab') {
1024
+ if (navigate(event.shiftKey ? -1 : 1))
1025
+ event.preventDefault();
1026
+ return;
1027
+ }
1028
+ if (event.shiftKey || current.caret.start !== current.caret.end)
1029
+ return;
1030
+ if (event.key === 'ArrowLeft' && current.caret.start === token.start) {
1031
+ if (navigate(-1))
1032
+ event.preventDefault();
1033
+ }
1034
+ if (event.key === 'ArrowRight' && current.caret.end === token.end) {
1035
+ if (navigate(1))
1036
+ event.preventDefault();
1037
+ }
1038
+ if (event.key === 'Backspace' &&
1039
+ current.caret.start === token.start &&
1040
+ token.start > 0) {
810
1041
  event.preventDefault();
811
- }
812
- if (event.key === 'Backspace' &&
813
- current.caret.start === token.start &&
814
- token.start > 0) {
815
- event.preventDefault();
816
- const count = Array.from(node.text.slice(0, token.start)).at(-1).length;
817
- apply(setText(current, node.text.slice(0, token.start - count) + node.text.slice(token.start), token.start - count));
818
- return;
819
- }
820
- if (event.key === 'Delete' &&
821
- current.caret.end === token.end &&
822
- token.end < node.text.length) {
823
- event.preventDefault();
824
- const count = Array.from(node.text.slice(token.end))[0].length;
825
- apply(setText(current, node.text.slice(0, token.end) + node.text.slice(token.end + count), token.end));
826
- return;
827
- }
828
- if (event.key === 'Backspace' && current.caret.start === 0) {
829
- const joined = joinPreviousLine(current);
830
- const next = joined !== current ? joined : unwrapPrevious(current);
831
- if (next !== current) {
1042
+ const count = Array.from(node.text.slice(0, token.start)).at(-1).length;
1043
+ apply(setText(current, node.text.slice(0, token.start - count) + node.text.slice(token.start), token.start - count));
1044
+ return;
1045
+ }
1046
+ if (event.key === 'Delete' &&
1047
+ current.caret.end === token.end &&
1048
+ token.end < node.text.length) {
832
1049
  event.preventDefault();
833
- apply(next);
1050
+ const count = Array.from(node.text.slice(token.end))[0].length;
1051
+ apply(setText(current, node.text.slice(0, token.end) + node.text.slice(token.end + count), token.end));
1052
+ return;
834
1053
  }
835
- }
836
- } })) : (_jsx("span", { className: "me-preview-token", "data-token-kind": token.kind, "aria-label": name, role: "button", tabIndex: -1, children: selected && token.text ? (_jsxs("span", { children: [token.text.slice(0, selected[0]), _jsx("mark", { className: "me-range-highlight", children: token.text.slice(selected[0], selected[1]) }), token.text.slice(selected[1])] })) : (token.text || (r.children.length === 1 ? '□' : '\u200b')) }))] }, key));
837
- });
838
- }) }, r.id));
839
- return (_jsxs("div", { className: "me-editor", "data-math-editor-owner": suggestionId, onCopy: copySelection, onCut: (event) => copySelection(event, true), onPaste: pasteSelection, role: "group", lang: locale, dir: mathLocaleDirection(locale), "aria-label": label ?? t('editor.label'), onBlur: (event) => {
1054
+ if (event.key === 'Backspace' && current.caret.start === 0) {
1055
+ const joined = joinPreviousLine(current);
1056
+ const next = joined !== current ? joined : unwrapPrevious(current);
1057
+ if (next !== current) {
1058
+ event.preventDefault();
1059
+ apply(next);
1060
+ }
1061
+ }
1062
+ } })) : (_jsx("span", { className: "me-preview-token", "data-token-kind": token.kind, "aria-label": name, role: "button", tabIndex: -1, children: selected && token.text ? (_jsxs("span", { children: [token.text.slice(0, selected[0]), _jsx("mark", { className: "me-range-highlight", children: token.text.slice(selected[0], selected[1]) }), token.text.slice(selected[1])] })) : (token.text || (r.children.length === 1 ? '□' : '\u200b')) }))] }, key));
1063
+ });
1064
+ }) }, r.id));
1065
+ };
1066
+ return (_jsxs("div", { className: "me-editor", "data-math-editor-owner": suggestionId, onKeyDownCapture: (event) => {
1067
+ if (event.key === 'F6' &&
1068
+ structureContext &&
1069
+ !composing &&
1070
+ !event.nativeEvent.isComposing) {
1071
+ const footer = contextFooter.current;
1072
+ const button = footer?.querySelector('button:not(:disabled)');
1073
+ if (button) {
1074
+ event.preventDefault();
1075
+ event.stopPropagation();
1076
+ if (footer?.contains(event.target))
1077
+ surface.current
1078
+ ?.querySelector('input.me-input')
1079
+ ?.focus({ preventScroll: true });
1080
+ else if (footer)
1081
+ focusContextAction(footer, 'F6');
1082
+ return;
1083
+ }
1084
+ }
1085
+ if (!event.nativeEvent.isComposing &&
1086
+ !composing &&
1087
+ event.altKey &&
1088
+ event.shiftKey &&
1089
+ event.code === 'KeyV') {
1090
+ event.preventDefault();
1091
+ event.stopPropagation();
1092
+ setQuickOpen(false);
1093
+ setSymbolsOpen(false);
1094
+ setLatexOpen(true);
1095
+ }
1096
+ }, onCopy: copySelection, onCut: (event) => copySelection(event, true), onPaste: pasteSelection, role: "group", lang: locale, dir: mathLocaleDirection(locale), "aria-label": label ?? t('editor.label'), onBlur: (event) => {
840
1097
  if (!event.currentTarget.contains(event.relatedTarget) &&
841
1098
  !suggestionMenu.current?.contains(event.relatedTarget) &&
842
1099
  !selectionMenu.current?.contains(event.relatedTarget) &&
@@ -846,19 +1103,74 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
846
1103
  setFocused(false);
847
1104
  setEditing(false);
848
1105
  }
849
- }, children: [toolbar && (_jsx(MathEditorToolbar, { locale: locale, kinds: Array.isArray(toolbar) ? toolbar : undefined, maxItems: toolbarMaxItems, excludedStructures: excludedStructures, composing: composing, hasRange: !!range, canWrap: canWrap, canUndo: !!history.past.length, canRedo: !!history.future.length, symbolsOpen: symbolsOpen, onInsertStructure: insertTool, onMatrixPreset: (value) => apply(value.startsWith('identity-')
1106
+ }, children: [toolbar && (_jsx(MathEditorToolbar, { locale: locale, kinds: Array.isArray(toolbar) ? toolbar : undefined, maxItems: toolbarMaxItems, excludedStructures: excludedStructures, composing: composing, hasRange: !!range || !!matrixRange, cellRange: !!matrixRange, canWrap: canWrap, canUndo: !!history.past.length, canRedo: !!history.future.length, symbolsOpen: symbolsOpen, onQuick: () => {
1107
+ setSymbolsOpen(false);
1108
+ setLatexOpen(false);
1109
+ setQuickOpen(true);
1110
+ }, onPasteLatex: () => {
1111
+ setQuickOpen(false);
1112
+ setSymbolsOpen(false);
1113
+ setLatexOpen(true);
1114
+ }, onInsertStructure: insertTool, onMatrixPreset: (value) => apply(value.startsWith('identity-')
850
1115
  ? insertIdentityMatrix(historyRef.current.present, Number(value.slice(9)))
851
- : insertMatrix(historyRef.current.present, Number(value))), onTemplate: (id) => apply(insertTemplate(historyRef.current.present, id, range)), onToggleSymbols: () => setSymbolsOpen((value) => !value), onInsertSymbol: (glyph) => {
1116
+ : insertMatrix(historyRef.current.present, Number(value))), onTemplate: (id) => {
1117
+ apply(insertTemplate(historyRef.current.present, id, range));
1118
+ preferences.record(`template-${id}`);
1119
+ }, onToggleSymbols: () => {
1120
+ setQuickOpen(false);
1121
+ setLatexOpen(false);
1122
+ setSymbolsOpen((value) => !value);
1123
+ }, onInsertSymbol: (glyph) => {
1124
+ preferences.record(`symbol-${glyph}`);
852
1125
  const current = historyRef.current.present, caret = current.caret;
853
1126
  const value = textNodes(current.document).find((node) => node.id === caret.id).text;
854
1127
  apply(setText(current, value.slice(0, caret.start) + glyph + value.slice(caret.end), caret.start + glyph.length));
855
- }, onUndo: () => publish(undo(historyRef.current), true), onRedo: () => publish(redo(historyRef.current), true), children: toolbarEnd })), toolbar && symbolsOpen && (_jsx(SymbolBrowser, { locale: locale, onClose: () => {
1128
+ }, onUndo: () => publish(undo(historyRef.current), true), onRedo: () => publish(redo(historyRef.current), true), children: toolbarEnd })), toolbar && (_jsx(PresentationControls, { state: state, locale: locale, disabled: composing || !!range || !!matrixRange, kinds: mathStructures
1129
+ .filter((item) => !excludedStructures.includes(item.kind) &&
1130
+ (!Array.isArray(toolbar) || toolbar.includes(item.kind)))
1131
+ .map((item) => item.kind), onChange: (id, change) => apply(setMathPresentation(historyRef.current.present, id, change)) })), quickOpen && (_jsx(QuickPanel, { locale: locale, preferences: preferences, allowedTemplates: toolbarTemplates(mathStructures
1132
+ .filter((item) => !excludedStructures.includes(item.kind) &&
1133
+ (!Array.isArray(toolbar) || toolbar.includes(item.kind)))
1134
+ .map((item) => item.kind)).map((item) => item.id), onInsert: (item) => {
1135
+ if (composing || matrixRange)
1136
+ return false;
1137
+ const current = historyRef.current.present;
1138
+ const next = item.template
1139
+ ? insertTemplate(current, item.template, range)
1140
+ : pasteFragment(current, { version: 1, rows: [row(item.glyph)] }, range);
1141
+ if (next === current)
1142
+ return false;
1143
+ apply(next);
1144
+ return true;
1145
+ }, onClose: () => {
1146
+ setQuickOpen(false);
1147
+ publish({ ...historyRef.current }, true);
1148
+ } })), latexOpen && (_jsx(LatexPastePanel, { locale: locale, onInsert: pasteLatex, onClose: () => {
1149
+ setLatexOpen(false);
1150
+ focusNext.current = true;
1151
+ setEditing(true);
1152
+ publish({ ...historyRef.current }, true);
1153
+ } })), toolbar && symbolsOpen && (_jsx(SymbolBrowser, { locale: locale, onClose: () => {
856
1154
  setSymbolsOpen(false);
857
1155
  surface.current?.focus();
858
1156
  }, onInsert: (glyph) => {
1157
+ preferences.record(`symbol-${glyph}`);
859
1158
  apply(pasteFragment(historyRef.current.present, { version: 1, rows: [row(glyph)] }, range));
860
1159
  setSymbolsOpen(false);
861
- } })), showTokenLegend && (_jsxs("div", { className: "me-token-legend", "aria-label": t('token.legend'), children: [_jsx("span", { "data-kind": "variable", children: t('token.variable') }), _jsx("span", { "data-kind": "constant", children: t('token.constant') }), _jsx("span", { "data-kind": "symbol", children: t('token.symbol') }), _jsx("span", { "data-kind": "function", children: t('token.function') }), _jsx("small", { children: t('editor.interactionHint') })] })), clipboardMessage && (_jsx("div", { className: "me-grid-selection-hint", role: "status", children: t(clipboardMessage) })), selectedGrid && (_jsx("div", { className: "me-grid-selection-hint", role: "status", children: t('grid.selectionHint') })), toolbar && matrixContext && (_jsxs("div", { className: "me-matrix-tools", role: "toolbar", "aria-label": t(matrixContext.matrix.type === 'matrix' ? 'matrix.edit' : 'grid.editRows'), children: [_jsxs("span", { children: [matrixContext.rows, " \u00D7 ", matrixContext.matrix.columns, ' ', _jsxs("small", { children: ["\u00B7", ' ', t('grid.position', { row: matrixContext.row + 1, column: matrixContext.column + 1 })] })] }), matrixContext.matrix.type === 'matrix' && (_jsx("select", { "aria-label": t('matrix.delimiters'), value: matrixContext.matrix.environment, disabled: composing, onChange: (event) => apply(setMatrixEnvironment(historyRef.current.present, event.target.value)), children: matrixEnvironments.map((item) => (_jsx("option", { value: item.value, children: t(item.label) }, item.value))) })), ['row', 'column']
1160
+ } })), showTokenLegend && (_jsxs("div", { className: "me-token-legend", "aria-label": t('token.legend'), children: [_jsx("span", { "data-kind": "variable", children: t('token.variable') }), _jsx("span", { "data-kind": "constant", children: t('token.constant') }), _jsx("span", { "data-kind": "symbol", children: t('token.symbol') }), _jsx("span", { "data-kind": "function", children: t('token.function') }), _jsx("small", { children: t('editor.interactionHint') })] })), clipboardMessage && (_jsx("div", { className: "me-grid-selection-hint", role: "status", children: t(clipboardMessage) })), selectedGrid && (_jsx("div", { className: "me-grid-selection-hint", role: "status", children: t('grid.selectionHint') })), toolbar && matrixContext && (_jsxs("div", { className: "me-matrix-tools", role: "toolbar", "aria-label": t(matrixContext.matrix.type === 'matrix' ? 'matrix.edit' : 'grid.editRows'), children: [_jsxs("span", { children: [matrixContext.rows, " \u00D7 ", matrixContext.matrix.columns, ' ', _jsxs("small", { children: ["\u00B7", ' ', t('grid.position', { row: matrixContext.row + 1, column: matrixContext.column + 1 })] })] }), matrixContext.matrix.type === 'matrix' && (_jsx("select", { "aria-label": t('matrix.delimiters'), value: matrixContext.matrix.environment, disabled: composing || !!matrixRange || !!range, onChange: (event) => apply(setMatrixEnvironment(historyRef.current.present, event.target.value)), children: matrixEnvironments.map((item) => (_jsx("option", { value: item.value, children: t(item.label) }, item.value))) })), matrixContext.matrix.type === 'matrix' && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", disabled: composing || !!range, onMouseDown: (event) => event.preventDefault(), onClick: () => selectCells({
1161
+ matrixId: matrixContext.matrix.id,
1162
+ anchor: matrixContext.cell,
1163
+ focus: matrixContext.cell,
1164
+ }), children: t('matrix.selectCells') }), _jsx("button", { type: "button", disabled: composing || !!range, onMouseDown: (event) => event.preventDefault(), onClick: () => selectCells({
1165
+ matrixId: matrixContext.matrix.id,
1166
+ anchor: 0,
1167
+ focus: matrixContext.matrix.slots.length - 1,
1168
+ }), children: t('matrix.selectAll') }), _jsx("button", { type: "button", disabled: composing || !!range, onMouseDown: (event) => event.preventDefault(), onClick: () => {
1169
+ const current = historyRef.current.present;
1170
+ const next = transposeMatrix(current, matrixRange?.matrixId);
1171
+ if (next !== current)
1172
+ apply(next);
1173
+ }, children: t('matrix.transpose') })] })), ['row', 'column']
862
1174
  .filter((axis) => axis === 'row' || matrixContext.matrix.type === 'matrix')
863
1175
  .flatMap((axis) => ['insert', 'delete'].map((action) => {
864
1176
  const size = axis === 'row' ? matrixContext.rows : matrixContext.matrix.columns;
@@ -868,7 +1180,10 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
868
1180
  ? axis === 'row'
869
1181
  ? 'Alt+Shift+ArrowUp'
870
1182
  : 'Alt+Shift+ArrowLeft'
871
- : undefined, disabled: composing || (action === 'delete' ? size === 1 : size >= MATRIX_MAX_SIZE), onMouseDown: (event) => event.preventDefault(), onClick: () => apply(resizeMatrix(historyRef.current.present, axis, action)), children: t(axis === 'row'
1183
+ : undefined, disabled: composing ||
1184
+ !!matrixRange ||
1185
+ !!range ||
1186
+ (action === 'delete' ? size === 1 : size >= MATRIX_MAX_SIZE), onMouseDown: (event) => event.preventDefault(), onClick: () => apply(resizeMatrix(historyRef.current.present, axis, action)), children: t(axis === 'row'
872
1187
  ? action === 'insert'
873
1188
  ? 'grid.addRow'
874
1189
  : 'grid.deleteRow'
@@ -879,10 +1194,48 @@ export function MathEditor({ apiRef, autoFocus = false, enterBehavior = 'newline
879
1194
  ? t('matrix.keyboardHint')
880
1195
  : `${t(matrixContext.matrix.type === 'aligned' ? 'aligned.inputHint' : 'cases.inputHint')} · ${t('grid.keyboardHint')}` })] })), showPopovers &&
881
1196
  candidates.length > 0 &&
882
- createPortal(_jsxs("div", { className: "me-suggestion-panel", "data-math-editor-owner": suggestionId, lang: locale, dir: mathLocaleDirection(locale), ref: suggestionMenu, onMouseDown: (event) => event.preventDefault(), children: [_jsxs("div", { className: "me-suggestion-heading", children: [t('suggestion.create', { query }), ' ', _jsx("span", { children: t(composing ? 'suggestion.composing' : 'suggestion.keyboardHint') })] }), _jsx("div", { className: "me-suggestions", id: suggestionId, role: "listbox", "aria-label": t('suggestion.label'), children: candidates.map((c, index) => (_jsxs("button", { type: "button", id: `${suggestionId}-${index}`, role: "option", disabled: composing, "aria-selected": !composing && index === selectedIndex, onClick: () => choose(index), children: [_jsx("span", { className: "me-candidate-glyph", children: c.glyph }), _jsxs("span", { children: [_jsx("strong", { children: c.label }), _jsx("small", { children: c.detail })] })] }, c.id))) }), _jsx("div", { className: "me-suggestion-footer", children: t(composing ? 'suggestion.imeHint' : 'suggestion.keepSource') })] }), surface.current?.closest('dialog, [role="dialog"]') ?? document.body), _jsx("div", { className: "me-surface", dir: "ltr", ref: surface, tabIndex: 0, "aria-label": t('selection.surface'), "data-mode": editing ? 'edit' : 'preview', onPointerDown: beginPointer, onPointerMove: movePointer, onPointerUp: endPointer, onPointerCancel: () => {
1197
+ createPortal(_jsxs("div", { className: "me-suggestion-panel", "data-math-editor-owner": suggestionId, lang: locale, dir: mathLocaleDirection(locale), ref: suggestionMenu, onMouseDown: (event) => event.preventDefault(), children: [_jsxs("div", { className: "me-suggestion-heading", children: [_jsx("span", { className: "me-suggestion-query", children: t('suggestion.create', { query }) }), _jsx("span", { children: t(composing ? 'suggestion.composing' : 'suggestion.keyboardHint') })] }), _jsx("div", { className: "me-suggestions", id: suggestionId, role: "listbox", "aria-label": t('suggestion.label'), children: candidates.map((c, index) => (_jsxs("button", { type: "button", id: `${suggestionId}-${index}`, role: "option", disabled: composing, "aria-selected": !composing && index === selectedIndex, onClick: () => choose(index), children: [_jsx("span", { className: "me-candidate-glyph", children: c.glyph }), _jsxs("span", { className: "me-candidate-copy", children: [_jsx("strong", { children: c.label }), _jsx("small", { children: c.detail })] })] }, c.id))) }), _jsx("div", { className: "me-suggestion-footer", children: t(composing ? 'suggestion.imeHint' : 'suggestion.keepSource') })] }), surface.current?.closest('dialog, [role="dialog"]') ?? document.body), _jsx("div", { className: "me-surface", dir: "ltr", ref: surface, tabIndex: 0, "aria-label": t('selection.surface'), "data-mode": editing ? 'edit' : 'preview', onPointerDown: beginPointer, onPointerMove: movePointer, onPointerUp: endPointer, onPointerCancel: () => {
883
1198
  drag.current = undefined;
884
- }, onKeyDown: selectionKey, children: _jsx("div", { className: "me-document-lines", children: documentRows(state.document).map((line, index) => (_jsxs("div", { className: "me-document-line", children: [showLineNumbers && documentRows(state.document).length > 1 && (_jsx("span", { className: "me-line-number", "aria-hidden": "true", children: index + 1 })), renderRow(line, labels.line(index))] }, line.id))) }) }), showPopovers &&
1199
+ }, onKeyDown: selectionKey, children: _jsx("div", { className: "me-document-lines", children: documentRows(state.document).map((line, index) => (_jsxs("div", { className: "me-document-line", children: [showLineNumbers && documentRows(state.document).length > 1 && (_jsx("span", { className: "me-line-number", "aria-hidden": "true", children: index + 1 })), renderRow(line, labels.line(index))] }, line.id))) }) }), structureContext && (_jsxs("div", { className: "me-context-footer", ref: contextFooter, role: "group", "aria-label": t('context.label'), onMouseDown: (event) => event.preventDefault(), onKeyDown: (event) => {
1200
+ event.stopPropagation();
1201
+ if (composing || event.nativeEvent.isComposing)
1202
+ return;
1203
+ if (focusContextAction(event.currentTarget, event.key)) {
1204
+ event.preventDefault();
1205
+ return;
1206
+ }
1207
+ if (event.key === 'Escape') {
1208
+ event.preventDefault();
1209
+ surface.current
1210
+ ?.querySelector('input.me-input')
1211
+ ?.focus({ preventScroll: true });
1212
+ }
1213
+ }, children: [fenceContext && (_jsxs(_Fragment, { children: [_jsx("span", { className: "me-context-label", children: t('context.current', {
1214
+ kind: t('presentation.fence') + ' ' + fences(fenceContext).join(' … '),
1215
+ }) }), _jsx("span", { className: "me-context-actions", children: Object.entries(fencePairs)
1216
+ .filter(([kind]) => !excludedStructures.includes(kind))
1217
+ .map(([kind, pair]) => {
1218
+ const label = t(mathStructures.find((item) => item.kind === kind).label);
1219
+ return (_jsx("button", { type: "button", className: "me-context-button", "aria-label": label, title: label, "aria-pressed": fenceContext.type === kind, disabled: composing, onClick: () => {
1220
+ if (!composing) {
1221
+ apply(changeContextFence(historyRef.current.present, fenceContext.id, kind));
1222
+ surface.current
1223
+ ?.querySelector('input.me-input')
1224
+ ?.focus({ preventScroll: true });
1225
+ }
1226
+ }, children: pair.join(' □ ') }, kind));
1227
+ }) }), _jsx("span", { className: "me-context-hint", children: t('context.fenceKeyboardHint') })] })), rootContext && (_jsxs(_Fragment, { children: [_jsx("span", { className: "me-context-label", children: t('context.current', { kind: t(`context.${rootContext.kind}`) }) }), _jsxs("span", { className: "me-context-actions", children: [rootContext.indexCaret && (_jsx("button", { type: "button", className: "me-context-button", disabled: composing, onClick: () => {
1228
+ const caret = rootContext.indexCaret;
1229
+ publish({
1230
+ ...historyRef.current,
1231
+ present: { ...historyRef.current.present, caret },
1232
+ }, true);
1233
+ }, children: t('context.editIndex') })), _jsx("button", { type: "button", className: "me-context-button", disabled: composing ||
1234
+ !rootContext.canTransform ||
1235
+ excludedStructures.includes(rootContext.target), onClick: () => {
1236
+ if (!composing)
1237
+ apply(transformRoot(historyRef.current.present, rootContext.id, rootContext.target));
1238
+ }, children: t('suggestion.transform', { kind: t(`context.${rootContext.target}`) }) })] }), _jsx("span", { className: "me-context-hint", children: t(rootContext.canTransform ? 'context.keyboardHint' : 'context.squareIndexRequired') })] }))] })), matrixSelection && (_jsx("div", { className: "me-grid-selection-hint", role: "status", children: t('matrix.rangeHint', { rows: matrixSelection.rows, columns: matrixSelection.columns }) })), showPopovers &&
885
1239
  selectionFragment &&
886
- createPortal(_jsxs("div", { className: "me-suggestion-panel", "data-math-editor-owner": suggestionId, ref: selectionMenu, role: "presentation", "aria-label": t('selection.toolbar'), onKeyDown: selectionKey, children: [_jsx("div", { className: "me-suggestion-heading", children: t(canWrap ? 'selection.highlighted' : 'selection.singleRowRequired') }), _jsx("div", { className: "me-suggestions", role: "listbox", "aria-label": t('selection.toolbar'), children: selectionChoices.map((s, index) => (_jsxs("button", { type: "button", role: "option", "aria-selected": index === selectionIndex, disabled: !canWrap, onMouseDown: (event) => event.preventDefault(), onClick: () => insertTool(s.kind), "aria-label": t('selection.wrap', { kind: t(s.label) }), children: [_jsx("span", { className: "me-candidate-glyph", children: s.glyph }), _jsxs("span", { children: [_jsx("strong", { children: t(s.label) }), _jsx("small", { children: t(s.detail) })] })] }, s.kind))) })] }), surface.current?.closest('dialog, [role="dialog"]') ?? document.body)] }));
1240
+ createPortal(_jsxs("div", { className: "me-suggestion-panel", "data-math-editor-owner": suggestionId, ref: selectionMenu, role: "presentation", "aria-label": t('selection.toolbar'), onKeyDown: selectionKey, children: [_jsx("div", { className: "me-suggestion-heading", children: t(canWrap ? 'selection.highlighted' : 'selection.singleRowRequired') }), _jsx("div", { className: "me-suggestions", role: "listbox", "aria-label": t('selection.toolbar'), children: selectionChoices.map((s, index) => (_jsxs("button", { type: "button", role: "option", "aria-selected": index === selectionIndex, disabled: !canWrap, onMouseDown: (event) => event.preventDefault(), onClick: () => insertTool(s.kind), "aria-label": t('selection.wrap', { kind: t(s.label) }), children: [_jsx("span", { className: "me-candidate-glyph", children: s.glyph }), _jsxs("span", { className: "me-candidate-copy", children: [_jsx("strong", { children: t(s.label) }), _jsx("small", { children: t(s.detail) })] })] }, s.kind))) })] }), surface.current?.closest('dialog, [role="dialog"]') ?? document.body)] }));
887
1241
  }
888
- //# sourceMappingURL=math-editor.js.map