@barocss/math-editor 0.4.0 → 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 (49) hide show
  1. package/API-SESSION.md +10 -0
  2. package/API-WEB-COMPONENT.md +2 -0
  3. package/CHANGELOG.md +61 -0
  4. package/EDITING-SCENARIOS.md +174 -0
  5. package/IMPLEMENTATION.md +95 -0
  6. package/LATEX-GUIDE.md +22 -0
  7. package/LATEX-MODEL.md +12 -0
  8. package/LATEX-SCOPE.md +10 -1
  9. package/README.md +73 -19
  10. package/RELEASING.md +81 -10
  11. package/RENDERING-TESTS.md +79 -0
  12. package/ROADMAP.md +132 -5
  13. package/VALIDATION.md +331 -0
  14. package/dist/context-tools.d.ts +21 -0
  15. package/dist/context-tools.js +37 -0
  16. package/dist/dom/context-keyboard.d.ts +2 -0
  17. package/dist/dom/context-keyboard.js +22 -0
  18. package/dist/dom/menu-position.d.ts +3 -0
  19. package/dist/dom/menu-position.js +45 -1
  20. package/dist/dom.d.ts +2 -0
  21. package/dist/dom.js +187 -38
  22. package/dist/latex.js +11 -0
  23. package/dist/locales/en.js +13 -1
  24. package/dist/locales/en.json +13 -1
  25. package/dist/locales/ko.js +13 -1
  26. package/dist/locales/ko.json +13 -1
  27. package/dist/math-editor.d.ts +3 -1
  28. package/dist/math-editor.js +394 -280
  29. package/dist/math-layout.d.ts +12 -0
  30. package/dist/math-layout.js +59 -0
  31. package/dist/math-spacing.d.ts +13 -0
  32. package/dist/math-spacing.js +87 -0
  33. package/dist/model.d.ts +4 -0
  34. package/dist/model.js +49 -4
  35. package/dist/root-transform.d.ts +19 -0
  36. package/dist/root-transform.js +69 -0
  37. package/dist/suggestions.d.ts +9 -0
  38. package/dist/suggestions.js +53 -0
  39. package/dist/web-component.js +9 -1
  40. package/package.json +11 -3
  41. package/src/fonts/KaTeX_Math-Italic.woff2 +0 -0
  42. package/src/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  43. package/src/fonts/README.md +18 -0
  44. package/src/shapes/README.md +21 -0
  45. package/src/shapes/parenthesis-bottom.svg +1 -0
  46. package/src/shapes/parenthesis-top.svg +1 -0
  47. package/src/shapes/parenthesis.svg +1 -0
  48. package/src/shapes/radical.svg +1 -0
  49. package/src/style.css +695 -44
@@ -1,4 +1,8 @@
1
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';
2
6
  import { setMathPresentation } from './presentation.js';
3
7
  import { PresentationControls } from './presentation-controls.js';
4
8
  import { createMathPreferences } from './preferences.js';
@@ -8,8 +12,8 @@ import { insertLatex } from './latex-insertion.js';
8
12
  import { LatexPastePanel } from './latex-paste-panel.js';
9
13
  import { followMathTheme } from './dom/theme.js';
10
14
  import { matrixPointerRange } from './dom/matrix-selection.js';
11
- import { positionMathMenu } from './dom/menu-position.js';
12
- import { fences } from './fences.js';
15
+ import { positionMathMenu, followMathMenuPosition } from './dom/menu-position.js';
16
+ import { fences, fencePairs } from './fences.js';
13
17
  import { parseLatex } from './latex.js';
14
18
  import { createEditorLabels } from './editor-labels.js';
15
19
  import { MathEditorToolbar } from './math-editor-toolbar.js';
@@ -17,6 +21,7 @@ import { SymbolBrowser } from './symbol-browser.js';
17
21
  import { insertTemplate } from './templates.js';
18
22
  import { translate, mathLocaleDirection, } from './i18n.js';
19
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';
20
25
  import { tokenizeMathText, tokenIndexAt } from './tokens.js';
21
26
  import { splitLine, joinPreviousLine } from './lines.js';
22
27
  import { moveVertical } from './vertical-navigation.js';
@@ -24,17 +29,18 @@ import { renderedCaretGeometry } from './dom/caret-geometry.js';
24
29
  import { insertIdentityMatrix, insertMatrix, activeGrid, MATRIX_MAX_SIZE, matrixEnvironments, resizeMatrix, setMatrixEnvironment, } from './matrix.js';
25
30
  import { MATH_MATRIX_CLIPBOARD_TYPE, parseMatrixFragment, matrixFragment, matrixFromTSV, matrixRangeBetween, resolveMatrixRange, extendMatrixRange, focusMatrixCell, copyMatrixRange, clearMatrixRange, pasteMatrixFragment, transposeMatrix, } from './matrix-range.js';
26
31
  import { createPortal } from 'react-dom';
27
- import { acceptSuggestion, findSuggestions, mathStructures } from './suggestions.js';
32
+ import { acceptSuggestion, findStateSuggestions, mathStructures } from './suggestions.js';
28
33
  import { useId, useImperativeHandle, useLayoutEffect, useRef, useState, } from 'react';
29
- 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';
30
35
  const structures = mathStructures;
31
36
  /** Native text fields own IME and local text selection; the tree owns structural editing. */
32
- 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, 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, }) {
33
38
  const t = (key, parameters) => translate(locale, key, parameters);
34
39
  const labels = createEditorLabels(locale);
35
40
  const [history, setHistory] = useState(() => createHistory(initialState(defaultValue)));
36
41
  const historyRef = useRef(history);
37
42
  const suggestionMenu = useRef(null);
43
+ const contextFooter = useRef(null);
38
44
  const selectionMenu = useRef(null);
39
45
  const [ownPreferences] = useState(createMathPreferences);
40
46
  const preferences = suppliedPreferences ?? ownPreferences;
@@ -77,11 +83,16 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
77
83
  const focusNext = useRef(autoFocus);
78
84
  const suggestionId = useId();
79
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;
80
91
  const matrixContext = activeGrid(state);
81
92
  const matrixSelection = matrixRange && resolveMatrixRange(state.document, matrixRange);
82
93
  const active = textNodes(state.document).find((n) => n.id === state.caret.id);
83
94
  const searchCaret = composing ? state.caret.end : state.caret.start;
84
- const matches = findSuggestions(active.text, searchCaret, locale);
95
+ const matches = findStateSuggestions(state, locale, searchCaret);
85
96
  matches.candidates = matches.candidates.filter((candidate) => !candidate.kind || !excludedStructures.includes(candidate.kind));
86
97
  const query = matches.query;
87
98
  const operandMenuKey = useRef('');
@@ -214,12 +225,12 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
214
225
  ? followMathTheme(surface.current.closest('.me-editor'), suggestionMenu.current)
215
226
  : undefined;
216
227
  position();
217
- window.addEventListener('resize', position);
218
- window.addEventListener('scroll', position, true);
228
+ const stopPosition = surface.current
229
+ ? followMathMenuPosition(surface.current, position)
230
+ : undefined;
219
231
  return () => {
220
232
  stopTheme?.();
221
- window.removeEventListener('resize', position);
222
- window.removeEventListener('scroll', position, true);
233
+ stopPosition?.();
223
234
  };
224
235
  }, [queryKey, candidates.length, active.id, showPopovers]);
225
236
  useLayoutEffect(() => {
@@ -578,15 +589,7 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
578
589
  }
579
590
  if (event.metaKey || event.ctrlKey)
580
591
  return;
581
- const point = !event.altKey && collapseKeyboardRange(current.document, range, event.key);
582
- if (point) {
583
- event.preventDefault();
584
- activate(point);
585
- return;
586
- }
587
- if (range &&
588
- selectionChoices.length &&
589
- (event.key === 'Enter' || (event.altKey && ['ArrowDown', 'ArrowUp'].includes(event.key)))) {
592
+ if (range && selectionChoices.length && ['Enter', 'ArrowDown', 'ArrowUp'].includes(event.key)) {
590
593
  event.preventDefault();
591
594
  if (event.key === 'Enter') {
592
595
  if (canWrap)
@@ -597,6 +600,12 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
597
600
  selectionChoices.length);
598
601
  return;
599
602
  }
603
+ const point = !event.altKey && collapseKeyboardRange(current.document, range, event.key);
604
+ if (point) {
605
+ event.preventDefault();
606
+ activate(point);
607
+ return;
608
+ }
600
609
  if (event.altKey)
601
610
  return;
602
611
  // A model selection must behave like a text selection when typing resumes.
@@ -682,12 +691,12 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
682
691
  ? followMathTheme(surface.current.closest('.me-editor'), selectionMenu.current)
683
692
  : undefined;
684
693
  position();
685
- window.addEventListener('scroll', position, true);
686
- window.addEventListener('resize', position);
694
+ const stopPosition = surface.current
695
+ ? followMathMenuPosition(surface.current, position)
696
+ : undefined;
687
697
  return () => {
688
- window.removeEventListener('scroll', position, true);
698
+ stopPosition?.();
689
699
  stopTheme?.();
690
- window.removeEventListener('resize', position);
691
700
  };
692
701
  }, [range, state.document, state.caret, showPopovers]);
693
702
  useLayoutEffect(() => {
@@ -721,292 +730,358 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
721
730
  apply(insertStructure(historyRef.current.present, kind, ['fraction', 'binomial', 'superscript', 'subscript'].includes(kind)));
722
731
  };
723
732
  // Nested slots share the same caret model. Only the active lexical token mounts an input.
724
- const renderRow = (r, name, role = 'expression') => (_jsx("span", { className: "me-row", "data-math-role": role, children: r.children.map((node) => {
725
- if (isGrid(node))
726
- 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'
727
- ? node.environment
728
- : node.type === 'cases'
729
- ? 'Bmatrix'
730
- : '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 &&
731
- matrixSelection.indices.includes(index)) ||
732
- 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));
733
- if (node.type === 'limit' || node.type === 'limsup' || node.type === 'liminf')
734
- 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));
735
- if (node.type !== 'text' &&
736
- [
737
- 'sum',
738
- 'product',
739
- 'integral',
740
- 'doubleIntegral',
741
- 'tripleIntegral',
742
- 'contourIntegral',
743
- ].includes(node.type))
744
- 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: {
745
- sum: '',
746
- product: '∏',
747
- integral: '',
748
- doubleIntegral: '',
749
- tripleIntegral: '',
750
- contourIntegral: '',
751
- }[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));
752
- if (node.type !== 'text')
753
- 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'
754
- ? index === 0
755
- ? 'numerator'
756
- : 'denominator'
757
- : 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'
758
773
  ? index === 0
759
- ? 'base'
760
- : 'exponent'
761
- : node.type === 'subscript'
774
+ ? 'numerator'
775
+ : 'denominator'
776
+ : node.type === 'superscript'
762
777
  ? index === 0
763
778
  ? 'base'
764
- : 'index'
765
- : node.type === 'root'
766
- ? 'radicand'
767
- : 'group') }, slot.id))), !!fences(node) && (_jsx("span", { className: "me-delimiter", "data-fence": fences(node)[1], "aria-hidden": "true", children: fences(node)[1] }))] }, node.id));
768
- const parts = tokensFor(node);
769
- return parts.map((token, tokenIndex) => {
770
- const key = `${node.id}:${tokenIndex}`;
771
- const selected = selectedText(node.id, token.start, token.end);
772
- const isActive = editing &&
773
- !range &&
774
- !matrixRange &&
775
- state.caret.id === node.id &&
776
- tokenIndexAt(parts, state.caret.start, state.caret.affinity) === tokenIndex;
777
- const caretFrom = (input) => ({
778
- id: node.id,
779
- start: token.start + (input.selectionStart ?? 0),
780
- end: token.start + (input.selectionEnd ?? 0),
781
- affinity: input.selectionStart === 0 ? 'forward' : 'backward',
782
- });
783
- const replaceToken = (input) => {
784
- const current = historyRef.current.present;
785
- const run = textNodes(current.document).find((n) => n.id === node.id);
786
- 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));
787
- };
788
- 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) => {
789
- if (element)
790
- inputs.current.set(key, element);
791
- else
792
- inputs.current.delete(key);
793
- }, "aria-label": name, "aria-description": t({
794
- variable: 'token.variable',
795
- constant: 'token.constant',
796
- symbol: 'token.symbol',
797
- space: 'token.space',
798
- empty: 'token.empty',
799
- draft: 'token.composing',
800
- }[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
801
- ? `${suggestionId}-${selectedIndex}`
802
- : undefined, className: "me-input", value: token.text, placeholder: r.children.length === 1 ? '□' : '', autoComplete: "off", spellCheck: false, onPointerDown: () => setSelectedGrid(undefined), onFocus: (event) => {
803
- setFocused(true);
804
- select(caretFrom(event.currentTarget));
805
- }, onSelect: (event) => select(caretFrom(event.currentTarget)), onCompositionStart: () => {
806
- compositionStart.current = historyRef.current;
807
- compositionToken.current = {
808
- id: node.id,
809
- index: tokenIndex,
810
- length: node.text.length,
811
- parts,
812
- };
813
- setComposing(true);
814
- }, onCompositionEnd: (event) => {
815
- const next = replaceToken(event.currentTarget);
816
- const base = compositionStart.current;
817
- compositionStart.current = null;
818
- compositionToken.current = null;
819
- setComposing(false);
820
- publish(commit(base ?? historyRef.current, next), true);
821
- }, onChange: (event) => {
822
- setSelectedGrid(undefined);
823
- const current = historyRef.current, next = replaceToken(event.currentTarget);
824
- publish(compositionStart.current
825
- ? { ...current, present: next }
826
- : commit(current, next), !compositionStart.current, !compositionStart.current);
827
- setCandidateIndex(0);
828
- setDismissed('');
829
- }, onKeyDown: (event) => {
830
- event.stopPropagation();
831
- if (event.nativeEvent.isComposing ||
832
- compositionStart.current ||
833
- event.keyCode === 229)
834
- return;
835
- if (matrixSelectionKey(event) || keyboardSelect(event))
836
- return;
837
- const current = historyRef.current.present;
838
- 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
+ }
839
894
  if (!event.metaKey &&
840
895
  !event.ctrlKey &&
841
896
  !event.altKey &&
842
897
  !event.shiftKey &&
843
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') {
844
911
  event.preventDefault();
845
- if (!event.repeat)
846
- apply(removeGrid(current, selectedGrid));
912
+ publish((event.shiftKey ? redo : undo)(historyRef.current), true);
847
913
  return;
848
914
  }
849
- setSelectedGrid(undefined);
850
- 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)) {
851
921
  event.preventDefault();
922
+ apply(resizeMatrix(current, event.key === 'ArrowUp' ? 'row' : 'column', 'delete'));
852
923
  return;
853
924
  }
854
- }
855
- if (!event.metaKey &&
856
- !event.ctrlKey &&
857
- !event.altKey &&
858
- !event.shiftKey &&
859
- ['Backspace', 'Delete'].includes(event.key)) {
860
- const grid = gridDeletionTarget(current, event.key === 'Backspace' ? -1 : 1);
861
- if (grid) {
925
+ if (event.altKey &&
926
+ !event.shiftKey &&
927
+ !event.ctrlKey &&
928
+ !event.metaKey &&
929
+ event.key === 'ArrowDown') {
862
930
  event.preventDefault();
863
- const empty = grid.slots.every((slot) => slot.children.every((child) => child.type === 'text' && !child.text));
864
- if (empty)
865
- apply(removeGrid(current, grid.id));
866
- else
867
- setSelectedGrid(grid.id);
931
+ setDismissed('');
932
+ setCandidateIndex(Math.max(0, matches.candidates.findIndex((item) => item.transformFenceId || item.transformRootId)));
933
+ operandMenuKey.current = queryKey;
868
934
  return;
869
935
  }
870
- }
871
- if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'z') {
872
- event.preventDefault();
873
- publish((event.shiftKey ? redo : undo)(historyRef.current), true);
874
- return;
875
- }
876
- if (event.altKey &&
877
- event.shiftKey &&
878
- !event.ctrlKey &&
879
- !event.metaKey &&
880
- activeGrid(current) &&
881
- ['ArrowUp', 'ArrowLeft'].includes(event.key)) {
882
- event.preventDefault();
883
- apply(resizeMatrix(current, event.key === 'ArrowUp' ? 'row' : 'column', 'delete'));
884
- return;
885
- }
886
- if (event.ctrlKey || event.metaKey || event.altKey)
887
- return;
888
- if (activeGrid(current) &&
889
- event.shiftKey &&
890
- ['Enter', ' '].includes(event.key)) {
891
- event.preventDefault();
892
- apply(resizeMatrix(current, event.key === 'Enter' ? 'row' : 'column', 'insert'));
893
- return;
894
- }
895
- if (selectedRange &&
896
- !event.metaKey &&
897
- !event.ctrlKey &&
898
- !event.altKey &&
899
- ['ArrowDown', 'ArrowUp', 'Enter'].includes(event.key)) {
900
- event.preventDefault();
901
- if (event.key === 'Enter') {
902
- if (canWrap)
903
- insertTool(selectionChoices[selectionIndex % selectionChoices.length].kind);
904
- }
905
- else
906
- setSelectionIndex((selectionIndex +
907
- (event.key === 'ArrowDown' ? 1 : selectionChoices.length - 1)) %
908
- selectionChoices.length);
909
- return;
910
- }
911
- if (candidates.length && ['ArrowDown', 'ArrowUp'].includes(event.key)) {
912
- event.preventDefault();
913
- const initialOperandChoice = candidates[0]?.wrapOperand && operandMenuKey.current !== queryKey;
914
- operandMenuKey.current = queryKey;
915
- setCandidateIndex(initialOperandChoice
916
- ? event.key === 'ArrowDown'
917
- ? 0
918
- : candidates.length - 1
919
- : (selectedIndex +
920
- (event.key === 'ArrowDown' ? 1 : candidates.length - 1)) %
921
- candidates.length);
922
- return;
923
- }
924
- if (event.key === 'Enter' &&
925
- candidates.length &&
926
- (!candidates[0].wrapOperand || operandMenuKey.current === queryKey)) {
927
- event.preventDefault();
928
- choose(selectedIndex);
929
- return;
930
- }
931
- if (event.key === 'Enter') {
932
- event.preventDefault();
933
- if (enterBehavior === 'commit' && !event.shiftKey) {
934
- 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'));
935
943
  return;
936
944
  }
937
- if (activeGrid(current)) {
938
- if (activeGrid(current).matrix.type !== 'matrix')
939
- 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;
940
960
  }
941
- else if (multiline)
942
- apply(splitLine(current));
943
- return;
944
- }
945
- if (event.key === 'Escape') {
946
- event.preventDefault();
947
- if (candidates.length)
948
- setDismissed(queryKey);
949
- else if (onCancel)
950
- onCancel();
951
- else {
952
- setEditing(false);
953
- 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;
954
976
  }
955
- return;
956
- }
957
- if (!event.shiftKey &&
958
- current.caret.start === current.caret.end &&
959
- ['ArrowUp', 'ArrowDown'].includes(event.key)) {
960
- const next = moveVertical(current, event.key === 'ArrowUp' ? -1 : 1, surface.current ? renderedCaretGeometry(surface.current) : undefined);
961
- 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)) {
962
983
  event.preventDefault();
963
- publish({ ...historyRef.current, present: next }, true);
984
+ choose(selectedIndex);
964
985
  return;
965
986
  }
966
- }
967
- if (event.key === 'Tab') {
968
- if (navigate(event.shiftKey ? -1 : 1))
987
+ if (event.key === 'Enter') {
969
988
  event.preventDefault();
970
- return;
971
- }
972
- if (event.shiftKey || current.caret.start !== current.caret.end)
973
- return;
974
- if (event.key === 'ArrowLeft' && current.caret.start === token.start) {
975
- 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') {
976
1002
  event.preventDefault();
977
- }
978
- if (event.key === 'ArrowRight' && current.caret.end === token.end) {
979
- 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) {
980
1041
  event.preventDefault();
981
- }
982
- if (event.key === 'Backspace' &&
983
- current.caret.start === token.start &&
984
- token.start > 0) {
985
- event.preventDefault();
986
- const count = Array.from(node.text.slice(0, token.start)).at(-1).length;
987
- apply(setText(current, node.text.slice(0, token.start - count) + node.text.slice(token.start), token.start - count));
988
- return;
989
- }
990
- if (event.key === 'Delete' &&
991
- current.caret.end === token.end &&
992
- token.end < node.text.length) {
993
- event.preventDefault();
994
- const count = Array.from(node.text.slice(token.end))[0].length;
995
- apply(setText(current, node.text.slice(0, token.end) + node.text.slice(token.end + count), token.end));
996
- return;
997
- }
998
- if (event.key === 'Backspace' && current.caret.start === 0) {
999
- const joined = joinPreviousLine(current);
1000
- const next = joined !== current ? joined : unwrapPrevious(current);
1001
- 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) {
1002
1049
  event.preventDefault();
1003
- 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;
1004
1053
  }
1005
- }
1006
- } })) : (_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));
1007
- });
1008
- }) }, r.id));
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
+ };
1009
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
+ }
1010
1085
  if (!event.nativeEvent.isComposing &&
1011
1086
  !composing &&
1012
1087
  event.altKey &&
@@ -1121,7 +1196,46 @@ export function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus
1121
1196
  candidates.length > 0 &&
1122
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: () => {
1123
1198
  drag.current = undefined;
1124
- }, 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))) }) }), matrixSelection && (_jsx("div", { className: "me-grid-selection-hint", role: "status", children: t('matrix.rangeHint', { rows: matrixSelection.rows, columns: matrixSelection.columns }) })), 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 &&
1125
1239
  selectionFragment &&
1126
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)] }));
1127
1241
  }