@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.
- package/ADAPTERS.md +25 -14
- package/API-JAVASCRIPT.md +1 -1
- package/API-REACT.md +5 -1
- package/API-SESSION.md +178 -6
- package/API-SOLID.md +1 -1
- package/API-SVELTE.md +1 -1
- package/API-VUE.md +1 -1
- package/API-WEB-COMPONENT.md +3 -1
- package/CHANGELOG.md +106 -0
- package/EDITING-SCENARIOS.md +174 -0
- package/EMBEDDING.md +4 -0
- package/IMPLEMENTATION.md +122 -2
- package/JSON-MODEL.md +1 -0
- package/LATEX-GUIDE.md +30 -1
- package/LATEX-MODEL.md +12 -0
- package/LATEX-SCOPE.md +12 -3
- package/LICENSE +1 -3
- package/README.md +201 -39
- package/RELEASING.md +81 -10
- package/RENDERING-TESTS.md +79 -0
- package/ROADMAP.md +220 -11
- package/STYLING.md +157 -0
- package/SUPPORT.md +24 -12
- package/VALIDATION.md +444 -4
- package/dist/context-tools.d.ts +21 -0
- package/dist/context-tools.js +37 -0
- package/dist/core.d.ts +5 -1
- package/dist/core.js +5 -1
- package/dist/document-codec.d.ts +0 -1
- package/dist/document-codec.js +0 -1
- package/dist/dom/caret-geometry.d.ts +3 -0
- package/dist/dom/caret-geometry.js +41 -0
- package/dist/dom/context-keyboard.d.ts +2 -0
- package/dist/dom/context-keyboard.js +22 -0
- package/dist/dom/elements.d.ts +1 -2
- package/dist/dom/elements.js +2 -3
- package/dist/dom/latex-panel.d.ts +10 -0
- package/dist/dom/latex-panel.js +59 -0
- package/dist/dom/matrix-selection.d.ts +11 -0
- package/dist/dom/matrix-selection.js +30 -0
- package/dist/dom/menu-position.d.ts +17 -2
- package/dist/dom/menu-position.js +129 -16
- package/dist/dom/presentation-controls.d.ts +4 -0
- package/dist/dom/presentation-controls.js +56 -0
- package/dist/dom/quick-panel.d.ts +12 -0
- package/dist/dom/quick-panel.js +102 -0
- package/dist/dom/selection.d.ts +29 -0
- package/dist/dom/selection.js +53 -0
- package/dist/dom/symbol-panel.d.ts +9 -0
- package/dist/dom/symbol-panel.js +146 -0
- package/dist/dom/theme.d.ts +6 -0
- package/dist/dom/theme.js +68 -0
- package/dist/dom/toolbar-catalog.d.ts +10 -0
- package/dist/dom/toolbar-catalog.js +35 -0
- package/dist/dom/toolbar.d.ts +13 -2
- package/dist/dom/toolbar.js +295 -28
- package/dist/dom.d.ts +9 -1
- package/dist/dom.js +682 -151
- package/dist/editor-labels.d.ts +0 -1
- package/dist/editor-labels.js +0 -1
- package/dist/enter-policy.d.ts +0 -1
- package/dist/enter-policy.js +0 -1
- package/dist/fences.d.ts +0 -1
- package/dist/fences.js +0 -1
- package/dist/i18n.d.ts +0 -1
- package/dist/i18n.js +0 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/dist/latex-insertion.d.ts +12 -0
- package/dist/latex-insertion.js +26 -0
- package/dist/latex-paste-panel.d.ts +3 -0
- package/dist/latex-paste-panel.js +18 -0
- package/dist/latex.d.ts +0 -1
- package/dist/latex.js +11 -1
- package/dist/lines.d.ts +0 -1
- package/dist/lines.js +0 -1
- package/dist/locales/en.js +39 -1
- package/dist/locales/en.json +39 -1
- package/dist/locales/ko.js +39 -1
- package/dist/locales/ko.json +39 -1
- package/dist/math-editor-toolbar.d.ts +4 -2
- package/dist/math-editor-toolbar.js +3 -4
- package/dist/math-editor.d.ts +7 -2
- package/dist/math-editor.js +661 -308
- package/dist/math-layout.d.ts +12 -0
- package/dist/math-layout.js +59 -0
- package/dist/math-spacing.d.ts +13 -0
- package/dist/math-spacing.js +87 -0
- package/dist/matrix-range.d.ts +40 -0
- package/dist/matrix-range.js +242 -0
- package/dist/matrix.d.ts +0 -1
- package/dist/matrix.js +0 -1
- package/dist/model.d.ts +4 -1
- package/dist/model.js +49 -5
- package/dist/outputs.d.ts +0 -1
- package/dist/outputs.js +0 -1
- package/dist/preferences.d.ts +24 -0
- package/dist/preferences.js +66 -0
- package/dist/presentation-controls.d.ts +10 -0
- package/dist/presentation-controls.js +10 -0
- package/dist/presentation.d.ts +13 -0
- package/dist/presentation.js +86 -0
- package/dist/quick-panel.d.ts +2 -0
- package/dist/quick-panel.js +17 -0
- package/dist/range.d.ts +2 -1
- package/dist/range.js +11 -1
- package/dist/react.d.ts +0 -1
- package/dist/react.js +0 -1
- package/dist/root-transform.d.ts +19 -0
- package/dist/root-transform.js +69 -0
- package/dist/session.d.ts +31 -2
- package/dist/session.js +108 -7
- package/dist/solid.d.ts +0 -1
- package/dist/solid.js +0 -1
- package/dist/suggestions.d.ts +13 -1
- package/dist/suggestions.js +153 -12
- package/dist/svelte.d.ts +0 -1
- package/dist/svelte.js +0 -1
- package/dist/symbol-browser.d.ts +0 -1
- package/dist/symbol-browser.js +0 -1
- package/dist/symbols.d.ts +0 -1
- package/dist/symbols.js +0 -1
- package/dist/templates.d.ts +0 -1
- package/dist/templates.js +0 -1
- package/dist/tokens.d.ts +0 -1
- package/dist/tokens.js +0 -1
- package/dist/vertical-navigation.d.ts +15 -0
- package/dist/vertical-navigation.js +145 -0
- package/dist/vue.d.ts +0 -1
- package/dist/vue.js +0 -1
- package/dist/web-component.d.ts +0 -1
- package/dist/web-component.js +9 -2
- package/package.json +11 -3
- package/src/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/src/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/src/fonts/README.md +18 -0
- package/src/shapes/README.md +21 -0
- package/src/shapes/parenthesis-bottom.svg +1 -0
- package/src/shapes/parenthesis-top.svg +1 -0
- package/src/shapes/parenthesis.svg +1 -0
- package/src/shapes/radical.svg +1 -0
- package/src/style.css +949 -173
- package/dist/core.d.ts.map +0 -1
- package/dist/core.js.map +0 -1
- package/dist/document-codec.d.ts.map +0 -1
- package/dist/document-codec.js.map +0 -1
- package/dist/dom/elements.d.ts.map +0 -1
- package/dist/dom/elements.js.map +0 -1
- package/dist/dom/menu-position.d.ts.map +0 -1
- package/dist/dom/menu-position.js.map +0 -1
- package/dist/dom/toolbar.d.ts.map +0 -1
- package/dist/dom/toolbar.js.map +0 -1
- package/dist/dom.d.ts.map +0 -1
- package/dist/dom.js.map +0 -1
- package/dist/editor-labels.d.ts.map +0 -1
- package/dist/editor-labels.js.map +0 -1
- package/dist/enter-policy.d.ts.map +0 -1
- package/dist/enter-policy.js.map +0 -1
- package/dist/fences.d.ts.map +0 -1
- package/dist/fences.js.map +0 -1
- package/dist/i18n.d.ts.map +0 -1
- package/dist/i18n.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/latex.d.ts.map +0 -1
- package/dist/latex.js.map +0 -1
- package/dist/lines.d.ts.map +0 -1
- package/dist/lines.js.map +0 -1
- package/dist/math-editor-toolbar.d.ts.map +0 -1
- package/dist/math-editor-toolbar.js.map +0 -1
- package/dist/math-editor.d.ts.map +0 -1
- package/dist/math-editor.js.map +0 -1
- package/dist/matrix.d.ts.map +0 -1
- package/dist/matrix.js.map +0 -1
- package/dist/model.d.ts.map +0 -1
- package/dist/model.js.map +0 -1
- package/dist/outputs.d.ts.map +0 -1
- package/dist/outputs.js.map +0 -1
- package/dist/range.d.ts.map +0 -1
- package/dist/range.js.map +0 -1
- package/dist/react.d.ts.map +0 -1
- package/dist/react.js.map +0 -1
- package/dist/session.d.ts.map +0 -1
- package/dist/session.js.map +0 -1
- package/dist/solid.d.ts.map +0 -1
- package/dist/solid.js.map +0 -1
- package/dist/suggestions.d.ts.map +0 -1
- package/dist/suggestions.js.map +0 -1
- package/dist/svelte.d.ts.map +0 -1
- package/dist/svelte.js.map +0 -1
- package/dist/symbol-browser.d.ts.map +0 -1
- package/dist/symbol-browser.js.map +0 -1
- package/dist/symbols.d.ts.map +0 -1
- package/dist/symbols.js.map +0 -1
- package/dist/templates.d.ts.map +0 -1
- package/dist/templates.js.map +0 -1
- package/dist/tokens.d.ts.map +0 -1
- package/dist/tokens.js.map +0 -1
- package/dist/vue.d.ts.map +0 -1
- package/dist/vue.js.map +0 -1
- package/dist/web-component.d.ts.map +0 -1
- package/dist/web-component.js.map +0 -1
package/dist/dom.js
CHANGED
|
@@ -1,45 +1,89 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { mathRunSpacing } from './math-spacing.js';
|
|
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 { mountLatexPanel } from './dom/latex-panel.js';
|
|
7
|
+
import { followMathTheme, syncMathTheme } from './dom/theme.js';
|
|
8
|
+
import { matrixPointerRange } from './dom/matrix-selection.js';
|
|
9
|
+
import { positionMathMenu, followMathMenuPosition } from './dom/menu-position.js';
|
|
10
|
+
import { fences, fencePairs } from './fences.js';
|
|
3
11
|
import { createEditorLabels } from './editor-labels.js';
|
|
4
12
|
import { mathEnterAction } from './enter-policy.js';
|
|
5
13
|
export { mountMathLatex, mountMathPreview, } from './outputs.js';
|
|
6
|
-
import {
|
|
14
|
+
import { elementFactory } from './dom/elements.js';
|
|
7
15
|
import { mountMathToolbar } from './dom/toolbar.js';
|
|
8
16
|
export { mountMathToolbar } from './dom/toolbar.js';
|
|
9
17
|
import { createMathSession, } from './session.js';
|
|
10
18
|
import { translate, mathLocaleDirection } from './i18n.js';
|
|
11
|
-
import { documentRows, isLiteralText, textNodes, setText, moveCaret, unwrapPrevious, gridDeletionTarget, removeGrid, isGrid, toLatex, } from './model.js';
|
|
12
|
-
import { activeGrid,
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
19
|
+
import { documentRows, isLiteralText, textNodes, setText, moveCaret, unwrapPrevious, unwrapEmptySlot, gridDeletionTarget, removeGrid, isGrid, toLatex, } from './model.js';
|
|
20
|
+
import { activeGrid, activeMatrix, resizeMatrix } from './matrix.js';
|
|
21
|
+
import { MATH_MATRIX_CLIPBOARD_TYPE, parseMatrixFragment, matrixFragment, matrixFromTSV, matrixRangeBetween, resolveMatrixRange, extendMatrixRange, focusMatrixCell, } from './matrix-range.js';
|
|
22
|
+
import { joinPreviousLine } from './lines.js';
|
|
23
|
+
import { moveVertical } from './vertical-navigation.js';
|
|
24
|
+
import { renderedCaretGeometry } from './dom/caret-geometry.js';
|
|
25
|
+
import { extendsMathSelection, nativeSelectionAnchor, createSelectionMenuState, textSelectionBounds, } from './dom/selection.js';
|
|
26
|
+
import { acceptSuggestion, findStateSuggestions, mathStructures } from './suggestions.js';
|
|
27
|
+
import { tokenizeMathText, tokenIndexAt } from './tokens.js';
|
|
28
|
+
import { fragmentLatex, parseFragment, MATH_CLIPBOARD_TYPE, extendKeyboardRange, collapseKeyboardRange, resolveRange, copyRange, wrappingKinds, } from './range.js';
|
|
17
29
|
/** Native DOM renderer. No React or framework runtime is imported by this module. */
|
|
18
30
|
export function mountMathEditor(host, initial = {}) {
|
|
31
|
+
const doc = host.ownerDocument;
|
|
32
|
+
const win = doc.defaultView;
|
|
33
|
+
const element = elementFactory(doc);
|
|
19
34
|
let options = { ...initial };
|
|
20
35
|
const session = initial.session ??
|
|
21
36
|
createMathSession({
|
|
22
37
|
document: initial.defaultValue,
|
|
38
|
+
preferences: initial.preferences,
|
|
23
39
|
locale: initial.locale,
|
|
24
40
|
mode: initial.mode,
|
|
25
41
|
});
|
|
26
42
|
const ownsSession = !initial.session;
|
|
27
43
|
if (initial.session)
|
|
28
44
|
session.configure({ locale: initial.locale, mode: initial.mode });
|
|
29
|
-
const root = element('span', 'me-editor me-dom-editor'), tools = element('span'), surface = element('span', 'me-surface'), status = element('span', 'me-dom-status');
|
|
30
|
-
root.append(tools, surface, status);
|
|
45
|
+
const root = element('span', 'me-editor me-dom-editor'), tools = element('span'), surface = element('span', 'me-surface'), status = element('span', 'me-dom-status'), contextFooter = element('span', 'me-context-footer');
|
|
46
|
+
root.append(tools, surface, contextFooter, status);
|
|
31
47
|
host.append(root);
|
|
32
48
|
surface.tabIndex = 0;
|
|
33
49
|
surface.setAttribute('role', 'group');
|
|
34
50
|
status.setAttribute('role', 'status');
|
|
35
51
|
const menu = element('div', 'me-suggestion-panel me-dom-menu');
|
|
36
52
|
menu.hidden = true;
|
|
37
|
-
(initial.menuHost ?? host.closest('dialog, [role="dialog"]') ??
|
|
53
|
+
(initial.menuHost ?? host.closest('dialog, [role="dialog"]') ?? doc.body).append(menu);
|
|
54
|
+
const stopTheme = followMathTheme(root, menu);
|
|
38
55
|
let editing = false, rendering = false, composing = false, destroyed = false, dismissed = '', operandMenuKey = '', candidateIndex = 0, selectedGrid;
|
|
39
56
|
let drag;
|
|
57
|
+
let dragMatrix;
|
|
58
|
+
const selectionMenu = createSelectionMenuState();
|
|
40
59
|
let toolbar;
|
|
60
|
+
let latexPanel;
|
|
61
|
+
const closeLatex = () => {
|
|
62
|
+
latexPanel?.destroy();
|
|
63
|
+
latexPanel = undefined;
|
|
64
|
+
editing = true;
|
|
65
|
+
render();
|
|
66
|
+
};
|
|
67
|
+
const openLatex = () => {
|
|
68
|
+
if (composing || destroyed || latexPanel)
|
|
69
|
+
return;
|
|
70
|
+
toolbar?.closePanels();
|
|
71
|
+
menu.hidden = true;
|
|
72
|
+
latexPanel = mountLatexPanel(root, {
|
|
73
|
+
locale: session.getSnapshot().locale,
|
|
74
|
+
onInsert: (source) => session.pasteLatex(source),
|
|
75
|
+
onClose: closeLatex,
|
|
76
|
+
});
|
|
77
|
+
};
|
|
41
78
|
const t = (value) => translate(session.getSnapshot().locale, value);
|
|
42
79
|
const currentInput = () => surface.querySelector('input');
|
|
80
|
+
const cancelDrag = () => {
|
|
81
|
+
const previous = drag;
|
|
82
|
+
drag = undefined;
|
|
83
|
+
dragMatrix = undefined;
|
|
84
|
+
if (previous && surface.hasPointerCapture(previous.pointerId))
|
|
85
|
+
surface.releasePointerCapture(previous.pointerId);
|
|
86
|
+
};
|
|
43
87
|
const notify = (key) => {
|
|
44
88
|
status.textContent = t(key);
|
|
45
89
|
};
|
|
@@ -57,10 +101,11 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
57
101
|
};
|
|
58
102
|
const candidates = () => {
|
|
59
103
|
const snapshot = session.getSnapshot(), state = snapshot.state, active = textNodes(state.document).find((n) => n.id === state.caret.id);
|
|
60
|
-
const result =
|
|
104
|
+
const result = findStateSuggestions(state, snapshot.locale);
|
|
61
105
|
if (!editing ||
|
|
62
106
|
isLiteralText(state.document, state.caret.id) ||
|
|
63
107
|
snapshot.range ||
|
|
108
|
+
snapshot.matrixRange ||
|
|
64
109
|
dismissed === `${active.id}:${active.text}:${state.caret.start}`)
|
|
65
110
|
result.candidates = [];
|
|
66
111
|
return result;
|
|
@@ -73,38 +118,94 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
73
118
|
dismissed = '';
|
|
74
119
|
candidateIndex = 0;
|
|
75
120
|
apply(acceptSuggestion(session.getSnapshot().state, result.query, item));
|
|
121
|
+
session.preferences.record(item.id);
|
|
76
122
|
}
|
|
77
123
|
};
|
|
78
|
-
const
|
|
124
|
+
const suggestionButton = (glyph, label, detail) => {
|
|
125
|
+
const button = element('button');
|
|
126
|
+
button.type = 'button';
|
|
127
|
+
button.setAttribute('role', 'option');
|
|
128
|
+
// Keep the concise option name while showing the same detail as the rich UI.
|
|
129
|
+
button.setAttribute('aria-label', `${glyph} ${label}`);
|
|
130
|
+
const copy = element('span', 'me-candidate-copy');
|
|
131
|
+
copy.append(element('strong', '', label), element('small', '', detail));
|
|
132
|
+
button.append(element('span', 'me-candidate-glyph', glyph), copy);
|
|
133
|
+
button.onmousedown = (event) => event.preventDefault();
|
|
134
|
+
button.onmousemove = (event) => {
|
|
135
|
+
// Rebuilding or scrolling the list can put a new row under a stationary
|
|
136
|
+
// pointer. Only real pointer movement may replace the keyboard choice.
|
|
137
|
+
if (composing || button.disabled || (!event.movementX && !event.movementY))
|
|
138
|
+
return;
|
|
139
|
+
const buttons = Array.from(button.parentElement.children);
|
|
140
|
+
const index = buttons.indexOf(button);
|
|
141
|
+
if (candidateIndex === index && button.getAttribute('aria-selected') === 'true')
|
|
142
|
+
return;
|
|
143
|
+
candidateIndex = index;
|
|
144
|
+
// Update the highlight in place: rebuilding would reset a wheel-scrolled list.
|
|
145
|
+
for (const option of buttons)
|
|
146
|
+
option.setAttribute('aria-selected', String(option === button));
|
|
147
|
+
// Host observers also see these attribute updates and must retain the input caret.
|
|
148
|
+
options.onRender?.();
|
|
149
|
+
};
|
|
150
|
+
return button;
|
|
151
|
+
};
|
|
152
|
+
const positionMenu = (anchor) => {
|
|
153
|
+
delete menu.dataset.compact;
|
|
154
|
+
positionMathMenu(menu, anchor, 340, options.menuAvoidElements?.());
|
|
155
|
+
// In a short dialog, leave the available space for selectable rows.
|
|
156
|
+
if (menu.getBoundingClientRect().height < 180) {
|
|
157
|
+
menu.dataset.compact = 'true';
|
|
158
|
+
positionMathMenu(menu, anchor, 340, options.menuAvoidElements?.());
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const scrollMenuSelection = () => {
|
|
162
|
+
const option = menu.querySelector('[aria-selected="true"]');
|
|
163
|
+
const list = option?.parentElement;
|
|
164
|
+
if (!option || !list)
|
|
165
|
+
return;
|
|
166
|
+
// Move only the options list, never the surrounding page or host editor.
|
|
167
|
+
const itemRect = option.getBoundingClientRect();
|
|
168
|
+
const listRect = list.getBoundingClientRect();
|
|
169
|
+
const scaleY = listRect.height / list.offsetHeight || 1;
|
|
170
|
+
if (itemRect.top < listRect.top)
|
|
171
|
+
list.scrollTop -= (listRect.top - itemRect.top) / scaleY;
|
|
172
|
+
else if (itemRect.bottom > listRect.bottom)
|
|
173
|
+
list.scrollTop += (itemRect.bottom - listRect.bottom) / scaleY;
|
|
174
|
+
};
|
|
175
|
+
const renderMenu = () => {
|
|
176
|
+
syncMathTheme(root, menu);
|
|
79
177
|
const result = candidates(), input = currentInput();
|
|
80
178
|
menu.replaceChildren();
|
|
81
179
|
const snapshot = session.getSnapshot();
|
|
82
180
|
const caret = snapshot.state.caret;
|
|
83
181
|
const selectedRange = snapshot.range ??
|
|
84
|
-
(caret.start !== caret.end
|
|
182
|
+
(input && caret.start !== caret.end
|
|
85
183
|
? {
|
|
86
184
|
anchor: { id: caret.id, offset: caret.start },
|
|
87
185
|
focus: { id: caret.id, offset: caret.end },
|
|
88
186
|
}
|
|
89
187
|
: undefined);
|
|
90
188
|
const fragment = selectedRange && copyRange(snapshot.state.document, selectedRange);
|
|
189
|
+
const showSelectionMenu = selectionMenu.sync(fragment ? selectedRange : undefined);
|
|
91
190
|
if (fragment) {
|
|
92
|
-
menu.
|
|
191
|
+
menu.dataset.kind = 'selection';
|
|
192
|
+
menu.hidden = !showSelectionMenu;
|
|
193
|
+
input?.setAttribute('aria-expanded', String(showSelectionMenu));
|
|
194
|
+
if (!showSelectionMenu)
|
|
195
|
+
return;
|
|
93
196
|
menu.lang = snapshot.locale;
|
|
94
197
|
menu.dir = mathLocaleDirection(snapshot.locale);
|
|
198
|
+
const canWrap = fragment.rows.length === 1 &&
|
|
199
|
+
!isLiteralText(snapshot.state.document, selectedRange.anchor.id);
|
|
200
|
+
menu.append(element('div', 'me-suggestion-heading', t(canWrap ? 'selection.highlighted' : 'selection.singleRowRequired')));
|
|
95
201
|
const list = element('div', 'me-suggestions');
|
|
96
202
|
list.setAttribute('role', 'listbox');
|
|
97
203
|
list.setAttribute('aria-label', t('selection.toolbar'));
|
|
98
204
|
for (const item of mathStructures.filter((item) => wrappingKinds.includes(item.kind))) {
|
|
99
|
-
const button =
|
|
100
|
-
button.type = 'button';
|
|
101
|
-
button.setAttribute('role', 'option');
|
|
205
|
+
const button = suggestionButton(item.glyph, t(item.label), t(item.detail));
|
|
102
206
|
button.setAttribute('aria-selected', String(list.children.length === candidateIndex));
|
|
103
|
-
button.disabled =
|
|
104
|
-
fragment.rows.length !== 1 ||
|
|
105
|
-
isLiteralText(snapshot.state.document, selectedRange.anchor.id);
|
|
207
|
+
button.disabled = !canWrap;
|
|
106
208
|
button.setAttribute('aria-label', translate(snapshot.locale, 'selection.wrap', { kind: t(item.label) }));
|
|
107
|
-
button.onmousedown = (event) => event.preventDefault();
|
|
108
209
|
button.onclick = () => {
|
|
109
210
|
if (!snapshot.range)
|
|
110
211
|
session.selectRange(selectedRange);
|
|
@@ -113,39 +214,149 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
113
214
|
list.append(button);
|
|
114
215
|
}
|
|
115
216
|
menu.append(list);
|
|
116
|
-
const selected = surface.querySelectorAll('.me-dom-selected');
|
|
217
|
+
const selected = surface.querySelectorAll('.me-dom-selected, .me-range-highlight');
|
|
117
218
|
const rect = (selected[selected.length - 1] ?? input ?? surface).getBoundingClientRect();
|
|
118
|
-
|
|
219
|
+
positionMenu(rect);
|
|
119
220
|
return;
|
|
120
221
|
}
|
|
222
|
+
menu.dataset.kind = 'suggestions';
|
|
121
223
|
menu.hidden = !input || !result.candidates.length;
|
|
122
224
|
input?.setAttribute('aria-expanded', String(!menu.hidden));
|
|
123
225
|
if (menu.hidden)
|
|
124
226
|
return;
|
|
125
227
|
menu.lang = session.getSnapshot().locale;
|
|
126
228
|
menu.dir = mathLocaleDirection(menu.lang);
|
|
229
|
+
const heading = element('div', 'me-suggestion-heading');
|
|
230
|
+
heading.append(element('span', 'me-suggestion-query', translate(snapshot.locale, 'suggestion.create', { query: result.query })), element('span', '', t(composing ? 'suggestion.composing' : 'suggestion.keyboardHint')));
|
|
127
231
|
const list = element('div', 'me-suggestions');
|
|
128
232
|
list.setAttribute('role', 'listbox');
|
|
129
233
|
list.setAttribute('aria-label', t('suggestion.label'));
|
|
130
234
|
candidateIndex = Math.min(candidateIndex, result.candidates.length - 1);
|
|
131
235
|
result.candidates.forEach((item, index) => {
|
|
132
|
-
const button =
|
|
133
|
-
button.
|
|
134
|
-
button.setAttribute('role', 'option');
|
|
135
|
-
button.setAttribute('aria-selected', String(index === candidateIndex));
|
|
236
|
+
const button = suggestionButton(item.glyph, item.label, item.detail);
|
|
237
|
+
button.setAttribute('aria-selected', String(!composing && index === candidateIndex));
|
|
136
238
|
button.disabled = composing;
|
|
137
|
-
button.onmousedown = (event) => event.preventDefault();
|
|
138
239
|
button.onclick = () => choose(index);
|
|
139
240
|
list.append(button);
|
|
140
241
|
});
|
|
141
|
-
menu.append(list);
|
|
242
|
+
menu.append(heading, list, element('div', 'me-suggestion-footer', t(composing ? 'suggestion.imeHint' : 'suggestion.keepSource')));
|
|
142
243
|
const rect = input.getBoundingClientRect();
|
|
143
|
-
|
|
244
|
+
positionMenu(rect);
|
|
144
245
|
input.setAttribute('aria-expanded', 'true');
|
|
145
246
|
};
|
|
247
|
+
const drawMenu = () => {
|
|
248
|
+
renderMenu();
|
|
249
|
+
// Keyup and selection events can redraw the list after keydown has scrolled it.
|
|
250
|
+
if (!menu.hidden)
|
|
251
|
+
scrollMenuSelection();
|
|
252
|
+
// Focus-only renders and native selection updates need the same notification
|
|
253
|
+
// as model edits, before a host's mutation observer restores its own selection.
|
|
254
|
+
if (!destroyed && !composing && !rendering)
|
|
255
|
+
options.onRender?.();
|
|
256
|
+
};
|
|
257
|
+
const dismissSelectionMenu = () => {
|
|
258
|
+
if (menu.hidden || menu.dataset.kind !== 'selection' || !selectionMenu.dismiss())
|
|
259
|
+
return false;
|
|
260
|
+
drawMenu();
|
|
261
|
+
return true;
|
|
262
|
+
};
|
|
263
|
+
menu.onkeydown = (event) => {
|
|
264
|
+
if (event.key !== 'Escape' || composing || event.isComposing || !dismissSelectionMenu())
|
|
265
|
+
return;
|
|
266
|
+
event.preventDefault();
|
|
267
|
+
event.stopPropagation();
|
|
268
|
+
(currentInput() ?? surface).focus();
|
|
269
|
+
};
|
|
270
|
+
const renderContextFooter = () => {
|
|
271
|
+
contextFooter.replaceChildren();
|
|
272
|
+
const snapshot = session.getSnapshot();
|
|
273
|
+
const structureContext = editing && options.contextTools !== false && !snapshot.range && !snapshot.matrixRange
|
|
274
|
+
? structureEditingContext(snapshot.state)
|
|
275
|
+
: undefined;
|
|
276
|
+
contextFooter.hidden = !structureContext;
|
|
277
|
+
if (!structureContext)
|
|
278
|
+
return;
|
|
279
|
+
contextFooter.setAttribute('role', 'group');
|
|
280
|
+
contextFooter.setAttribute('aria-label', t('context.label'));
|
|
281
|
+
const fence = structureContext.fence;
|
|
282
|
+
if (fence) {
|
|
283
|
+
const pair = fences(fence);
|
|
284
|
+
contextFooter.append(element('span', 'me-context-label', translate(snapshot.locale, 'context.current', {
|
|
285
|
+
kind: t('presentation.fence') + ' ' + pair.join(' … '),
|
|
286
|
+
})));
|
|
287
|
+
const actions = element('span', 'me-context-actions');
|
|
288
|
+
for (const [kind, pair] of Object.entries(fencePairs)) {
|
|
289
|
+
const label = t(mathStructures.find((item) => item.kind === kind).label);
|
|
290
|
+
const button = element('button', 'me-context-button', pair.join(' □ '));
|
|
291
|
+
button.type = 'button';
|
|
292
|
+
button.disabled = composing;
|
|
293
|
+
button.title = label;
|
|
294
|
+
button.setAttribute('aria-label', label);
|
|
295
|
+
button.setAttribute('aria-pressed', String(fence.type === kind));
|
|
296
|
+
button.onclick = () => {
|
|
297
|
+
if (!composing) {
|
|
298
|
+
apply(changeContextFence(session.getSnapshot().state, fence.id, kind));
|
|
299
|
+
currentInput()?.focus({ preventScroll: true });
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
actions.append(button);
|
|
303
|
+
}
|
|
304
|
+
contextFooter.append(actions, element('span', 'me-context-hint', t('context.fenceKeyboardHint')));
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const context = structureContext.root;
|
|
308
|
+
contextFooter.append(element('span', 'me-context-label', translate(snapshot.locale, 'context.current', { kind: t(`context.${context.kind}`) })));
|
|
309
|
+
const actions = element('span', 'me-context-actions');
|
|
310
|
+
if (context.indexCaret) {
|
|
311
|
+
const index = element('button', 'me-context-button', t('context.editIndex'));
|
|
312
|
+
index.type = 'button';
|
|
313
|
+
index.disabled = composing;
|
|
314
|
+
index.onclick = () => {
|
|
315
|
+
if (!composing)
|
|
316
|
+
session.select(context.indexCaret);
|
|
317
|
+
};
|
|
318
|
+
actions.append(index);
|
|
319
|
+
}
|
|
320
|
+
const convert = element('button', 'me-context-button', translate(snapshot.locale, 'suggestion.transform', {
|
|
321
|
+
kind: t(`context.${context.target}`),
|
|
322
|
+
}));
|
|
323
|
+
convert.type = 'button';
|
|
324
|
+
convert.disabled = composing || !context.canTransform;
|
|
325
|
+
convert.onclick = () => {
|
|
326
|
+
if (!composing)
|
|
327
|
+
apply(transformRoot(session.getSnapshot().state, context.id, context.target));
|
|
328
|
+
};
|
|
329
|
+
actions.append(convert);
|
|
330
|
+
contextFooter.append(actions, element('span', 'me-context-hint', t(context.canTransform ? 'context.keyboardHint' : 'context.squareIndexRequired')));
|
|
331
|
+
};
|
|
332
|
+
contextFooter.onmousedown = (event) => event.preventDefault();
|
|
333
|
+
contextFooter.onkeydown = (event) => {
|
|
334
|
+
event.stopPropagation();
|
|
335
|
+
if (composing || event.isComposing)
|
|
336
|
+
return;
|
|
337
|
+
if (focusContextAction(contextFooter, event.key === 'F6' ? '' : event.key)) {
|
|
338
|
+
event.preventDefault();
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (event.key === 'Escape' || event.key === 'F6') {
|
|
342
|
+
event.preventDefault();
|
|
343
|
+
currentInput()?.focus({ preventScroll: true });
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
const focusContextFooter = (event) => {
|
|
347
|
+
if (event.key !== 'F6' || contextFooter.hidden || composing || event.isComposing)
|
|
348
|
+
return false;
|
|
349
|
+
if (!focusContextAction(contextFooter, 'F6'))
|
|
350
|
+
return false;
|
|
351
|
+
event.preventDefault();
|
|
352
|
+
return true;
|
|
353
|
+
};
|
|
146
354
|
const render = () => {
|
|
147
355
|
if (destroyed || composing || rendering)
|
|
148
356
|
return;
|
|
357
|
+
// External caret/configuration changes replace the input and end its pending native drag.
|
|
358
|
+
if (drag?.input && !drag.moved)
|
|
359
|
+
cancelDrag();
|
|
149
360
|
rendering = true;
|
|
150
361
|
const snapshot = session.getSnapshot(), { state } = snapshot;
|
|
151
362
|
root.dataset.mode = snapshot.mode;
|
|
@@ -156,90 +367,139 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
156
367
|
surface.setAttribute('aria-label', t('selection.surface'));
|
|
157
368
|
surface.replaceChildren();
|
|
158
369
|
const spans = snapshot.range && resolveRange(state.document, snapshot.range);
|
|
370
|
+
const matrixSelection = snapshot.matrixRange && resolveMatrixRange(state.document, snapshot.matrixRange);
|
|
371
|
+
if (matrixSelection)
|
|
372
|
+
status.textContent = translate(snapshot.locale, 'matrix.rangeHint', {
|
|
373
|
+
rows: matrixSelection.rows,
|
|
374
|
+
columns: matrixSelection.columns,
|
|
375
|
+
});
|
|
376
|
+
else
|
|
377
|
+
status.textContent = '';
|
|
159
378
|
const selected = (r) => spans?.find((span) => span.row.id === r.id);
|
|
160
379
|
const labels = createEditorLabels(snapshot.locale);
|
|
380
|
+
const layout = mathLayout(state.document, snapshot.mode);
|
|
161
381
|
const renderRow = (r, name, inheritedSelection = false) => {
|
|
162
382
|
const view = element('span', 'me-row');
|
|
383
|
+
const spacing = mathRunSpacing(r);
|
|
384
|
+
view.dataset.layoutStyle = layout.rows.get(r.id);
|
|
163
385
|
const selection = selected(r);
|
|
164
386
|
let offset = 0;
|
|
165
387
|
for (const child of r.children) {
|
|
166
388
|
const length = child.type === 'text' ? child.text.length : 1;
|
|
389
|
+
const textSelection = child.type === 'text'
|
|
390
|
+
? textSelectionBounds(length, offset, selection, inheritedSelection)
|
|
391
|
+
: undefined;
|
|
167
392
|
const highlighted = inheritedSelection ||
|
|
168
393
|
(!!selection && offset < selection.end && offset + length > selection.start);
|
|
169
394
|
offset += length;
|
|
170
395
|
if (child.type === 'text') {
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
396
|
+
const parts = isLiteralText(state.document, child.id)
|
|
397
|
+
? [{ text: child.text, start: 0, end: child.text.length, kind: 'draft' }]
|
|
398
|
+
: tokenizeMathText(child.text);
|
|
399
|
+
const activeIndex = tokenIndexAt(parts, state.caret.start, state.caret.affinity);
|
|
400
|
+
for (const [index, token] of parts.entries()) {
|
|
401
|
+
const boundary = !token.text && r.children.length > 1;
|
|
402
|
+
const run = element('span', `me-run${token.text ? '' : ' me-empty'}${boundary ? ' me-empty-boundary' : ''}`);
|
|
403
|
+
run.dataset.runId = child.id;
|
|
404
|
+
run.dataset.value = token.text;
|
|
405
|
+
run.dataset.tokenKind = token.kind;
|
|
406
|
+
if (!isLiteralText(state.document, child.id)) {
|
|
407
|
+
const gap = spacing.get(`${child.id}:${token.start}`);
|
|
408
|
+
run.dataset.mathSpacing = 'true';
|
|
409
|
+
run.style.setProperty('--me-space-before', String(gap?.before ?? 0));
|
|
410
|
+
run.style.setProperty('--me-space-after', String(gap?.after ?? 0));
|
|
411
|
+
run.style.setProperty('--me-tight-before', String(gap?.tightBefore ?? 0));
|
|
412
|
+
run.style.setProperty('--me-tight-after', String(gap?.tightAfter ?? 0));
|
|
413
|
+
}
|
|
414
|
+
run.dataset.tokenStart = String(token.start);
|
|
415
|
+
run.dataset.tokenEnd = String(token.end);
|
|
416
|
+
const mirror = element('span', 'me-measure', token.text || (boundary ? '' : '□'));
|
|
417
|
+
mirror.setAttribute('aria-hidden', 'true');
|
|
418
|
+
run.append(mirror);
|
|
419
|
+
const active = editing &&
|
|
420
|
+
!snapshot.range &&
|
|
421
|
+
!snapshot.matrixRange &&
|
|
422
|
+
state.caret.id === child.id &&
|
|
423
|
+
index === activeIndex;
|
|
424
|
+
if (active) {
|
|
425
|
+
const input = element('input', 'me-input');
|
|
426
|
+
input.value = token.text;
|
|
427
|
+
input.placeholder = boundary ? '' : '□';
|
|
428
|
+
input.dataset.tokenStart = String(token.start);
|
|
429
|
+
input.dataset.tokenEnd = String(token.end);
|
|
430
|
+
input.setAttribute('aria-label', name);
|
|
431
|
+
input.setAttribute('role', 'combobox');
|
|
432
|
+
input.setAttribute('aria-expanded', 'false');
|
|
433
|
+
input.autocomplete = 'off';
|
|
434
|
+
input.spellcheck = false;
|
|
435
|
+
const syncCaret = () => {
|
|
436
|
+
if (input.isConnected && !rendering && !composing) {
|
|
437
|
+
rendering = true;
|
|
438
|
+
session.select({
|
|
439
|
+
id: child.id,
|
|
440
|
+
start: token.start + (input.selectionStart ?? 0),
|
|
441
|
+
end: token.start + (input.selectionEnd ?? 0),
|
|
442
|
+
affinity: input.selectionStart === 0 ? 'forward' : 'backward',
|
|
443
|
+
});
|
|
444
|
+
rendering = false;
|
|
445
|
+
drawMenu();
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
const replaceToken = () => {
|
|
449
|
+
const current = session.getSnapshot().state;
|
|
450
|
+
const text = textNodes(current.document).find((node) => node.id === child.id).text;
|
|
451
|
+
return setText(current, text.slice(0, token.start) + input.value + text.slice(token.end), token.start + (input.selectionStart ?? 0), token.start + (input.selectionEnd ?? 0));
|
|
452
|
+
};
|
|
453
|
+
input.onselect = syncCaret;
|
|
454
|
+
input.onkeyup = syncCaret;
|
|
455
|
+
input.oninput = () => {
|
|
456
|
+
if (composing) {
|
|
457
|
+
// Do not split or replace this input before the browser commits its preedit.
|
|
458
|
+
mirror.textContent = input.value || '□';
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
dismissed = '';
|
|
462
|
+
candidateIndex = 0;
|
|
463
|
+
apply(replaceToken());
|
|
464
|
+
};
|
|
465
|
+
input.addEventListener('compositionstart', () => {
|
|
466
|
+
cancelDrag();
|
|
467
|
+
composing = true;
|
|
468
|
+
toolbar?.setDisabled(true);
|
|
469
|
+
renderContextFooter();
|
|
470
|
+
menu.hidden = true;
|
|
471
|
+
});
|
|
472
|
+
input.addEventListener('compositionend', () => {
|
|
473
|
+
composing = false;
|
|
474
|
+
toolbar?.setDisabled(false);
|
|
475
|
+
dismissed = '';
|
|
476
|
+
apply(replaceToken());
|
|
477
|
+
});
|
|
478
|
+
input.onkeydown = keydown;
|
|
479
|
+
run.append(input);
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
const preview = element('span', 'me-preview-token');
|
|
483
|
+
preview.setAttribute('aria-label', name);
|
|
484
|
+
preview.setAttribute('role', 'button');
|
|
485
|
+
preview.tabIndex = -1;
|
|
486
|
+
const selected = textSelectionBounds(token.text.length, token.start, textSelection);
|
|
487
|
+
if (selected) {
|
|
488
|
+
preview.append(doc.createTextNode(token.text.slice(0, selected.start)), element('mark', 'me-range-highlight', token.text.slice(selected.start, selected.end)), doc.createTextNode(token.text.slice(selected.end)));
|
|
489
|
+
run.dataset.rangeSelected = 'true';
|
|
206
490
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
apply(next);
|
|
211
|
-
};
|
|
212
|
-
input.addEventListener('compositionstart', () => {
|
|
213
|
-
composing = true;
|
|
214
|
-
menu.hidden = true;
|
|
215
|
-
});
|
|
216
|
-
input.addEventListener('compositionend', () => {
|
|
217
|
-
composing = false;
|
|
218
|
-
dismissed = '';
|
|
219
|
-
apply(setText(session.getSnapshot().state, input.value, input.selectionStart ?? 0, input.selectionEnd ?? 0));
|
|
220
|
-
});
|
|
221
|
-
input.onkeydown = keydown;
|
|
222
|
-
run.append(input);
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
const preview = element('span', 'me-preview-token');
|
|
226
|
-
preview.setAttribute('aria-label', name);
|
|
227
|
-
preview.setAttribute('role', 'button');
|
|
228
|
-
preview.tabIndex = -1;
|
|
229
|
-
for (const token of tokenizeMathText(child.text)) {
|
|
230
|
-
const part = element('span', `me-dom-${token.kind}`, token.text || (boundary ? '' : '□'));
|
|
231
|
-
preview.append(part);
|
|
491
|
+
else
|
|
492
|
+
preview.textContent = token.text || (boundary ? '' : '□');
|
|
493
|
+
run.append(preview);
|
|
232
494
|
}
|
|
233
|
-
|
|
234
|
-
run.classList.add('me-dom-selected');
|
|
235
|
-
run.append(preview);
|
|
495
|
+
view.append(run);
|
|
236
496
|
}
|
|
237
|
-
view.append(run);
|
|
238
497
|
continue;
|
|
239
498
|
}
|
|
240
499
|
if (isGrid(child)) {
|
|
241
500
|
const grid = element('span', 'me-matrix');
|
|
242
501
|
grid.dataset.structure = child.type;
|
|
502
|
+
grid.dataset.matrixId = child.id;
|
|
243
503
|
grid.dataset.environment =
|
|
244
504
|
child.type === 'matrix'
|
|
245
505
|
? child.environment
|
|
@@ -252,6 +512,10 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
252
512
|
cells.style.gridTemplateColumns = `repeat(${child.columns},max-content)`;
|
|
253
513
|
child.slots.forEach((slot, i) => {
|
|
254
514
|
const cell = element('span', 'me-matrix-cell');
|
|
515
|
+
cell.dataset.cellIndex = String(i);
|
|
516
|
+
if (matrixSelection?.matrix.id === child.id && matrixSelection.indices.includes(i)) {
|
|
517
|
+
cell.dataset.cellSelected = 'true';
|
|
518
|
+
}
|
|
255
519
|
cell.append(renderRow(slot, labels.grid(name, child.type, i, child.columns), highlighted));
|
|
256
520
|
cells.append(cell);
|
|
257
521
|
});
|
|
@@ -261,6 +525,19 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
261
525
|
}
|
|
262
526
|
const structure = element('span', `me-structure me-${child.type}`);
|
|
263
527
|
structure.dataset.structure = child.type;
|
|
528
|
+
const structureSpacing = spacing.get(child.id);
|
|
529
|
+
if (structureSpacing) {
|
|
530
|
+
structure.dataset.mathSpacing = 'true';
|
|
531
|
+
structure.style.setProperty('--me-space-before', String(structureSpacing.before));
|
|
532
|
+
structure.style.setProperty('--me-space-after', String(structureSpacing.after));
|
|
533
|
+
structure.style.setProperty('--me-tight-before', String(structureSpacing.tightBefore ?? 0));
|
|
534
|
+
structure.style.setProperty('--me-tight-after', String(structureSpacing.tightAfter ?? 0));
|
|
535
|
+
}
|
|
536
|
+
const operatorLayout = layout.operators.get(child.id);
|
|
537
|
+
if (operatorLayout) {
|
|
538
|
+
structure.dataset.operatorStyle = operatorLayout.style;
|
|
539
|
+
structure.dataset.layoutLimits = operatorLayout.placement;
|
|
540
|
+
}
|
|
264
541
|
if (child.mathStyle)
|
|
265
542
|
structure.dataset.mathStyle = child.mathStyle;
|
|
266
543
|
if (['limit', 'limsup', 'liminf'].includes(child.type)) {
|
|
@@ -341,11 +618,51 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
341
618
|
const input = currentInput();
|
|
342
619
|
if (input) {
|
|
343
620
|
input.focus();
|
|
344
|
-
|
|
621
|
+
const start = Number(input.dataset.tokenStart);
|
|
622
|
+
input.setSelectionRange(state.caret.start - start, state.caret.end - start);
|
|
345
623
|
}
|
|
624
|
+
renderContextFooter();
|
|
346
625
|
rendering = false;
|
|
347
626
|
drawMenu();
|
|
348
627
|
};
|
|
628
|
+
function matrixSelectionKey(event) {
|
|
629
|
+
const snapshot = session.getSnapshot(), selection = snapshot.matrixRange;
|
|
630
|
+
if (!selection || composing || event.isComposing)
|
|
631
|
+
return false;
|
|
632
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'a') {
|
|
633
|
+
event.preventDefault();
|
|
634
|
+
session.execute({ type: 'matrix-select', all: true });
|
|
635
|
+
return true;
|
|
636
|
+
}
|
|
637
|
+
if (event.metaKey || event.ctrlKey || event.altKey)
|
|
638
|
+
return false;
|
|
639
|
+
if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.key)) {
|
|
640
|
+
event.preventDefault();
|
|
641
|
+
const next = extendMatrixRange(snapshot.state.document, selection, event.key);
|
|
642
|
+
if (event.shiftKey)
|
|
643
|
+
session.selectMatrixRange(next);
|
|
644
|
+
else {
|
|
645
|
+
editing = true;
|
|
646
|
+
session.select(focusMatrixCell(snapshot.state, next.matrixId, next.focus).caret);
|
|
647
|
+
}
|
|
648
|
+
return true;
|
|
649
|
+
}
|
|
650
|
+
if (['Escape', 'Enter', 'F2'].includes(event.key)) {
|
|
651
|
+
event.preventDefault();
|
|
652
|
+
editing = true;
|
|
653
|
+
session.selectMatrixRange();
|
|
654
|
+
render();
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
if (['Backspace', 'Delete'].includes(event.key) || Array.from(event.key).length === 1) {
|
|
658
|
+
event.preventDefault();
|
|
659
|
+
perform(['Backspace', 'Delete'].includes(event.key)
|
|
660
|
+
? { type: 'matrix-clear' }
|
|
661
|
+
: { type: 'text', value: event.key });
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
return false;
|
|
665
|
+
}
|
|
349
666
|
function keyboardSelect(event) {
|
|
350
667
|
if (composing ||
|
|
351
668
|
event.isComposing ||
|
|
@@ -361,11 +678,70 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
361
678
|
surface.focus();
|
|
362
679
|
return true;
|
|
363
680
|
}
|
|
681
|
+
function selectionMenuKey(event) {
|
|
682
|
+
if (menu.hidden ||
|
|
683
|
+
menu.dataset.kind !== 'selection' ||
|
|
684
|
+
event.shiftKey ||
|
|
685
|
+
event.ctrlKey ||
|
|
686
|
+
event.metaKey ||
|
|
687
|
+
!['ArrowUp', 'ArrowDown', 'Enter'].includes(event.key))
|
|
688
|
+
return false;
|
|
689
|
+
const items = [...menu.querySelectorAll('[role="option"]')];
|
|
690
|
+
if (!items.length)
|
|
691
|
+
return false;
|
|
692
|
+
// The visible menu owns vertical arrows before a model range can collapse.
|
|
693
|
+
event.preventDefault();
|
|
694
|
+
if (event.key === 'Enter')
|
|
695
|
+
items[candidateIndex % items.length].click();
|
|
696
|
+
else {
|
|
697
|
+
candidateIndex =
|
|
698
|
+
(candidateIndex + (event.key === 'ArrowDown' ? 1 : items.length - 1)) % items.length;
|
|
699
|
+
drawMenu();
|
|
700
|
+
}
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
function keyboardCollapse(event) {
|
|
704
|
+
if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey)
|
|
705
|
+
return false;
|
|
706
|
+
const snapshot = session.getSnapshot();
|
|
707
|
+
const point = collapseKeyboardRange(snapshot.state.document, snapshot.range, event.key);
|
|
708
|
+
if (!point)
|
|
709
|
+
return false;
|
|
710
|
+
event.preventDefault();
|
|
711
|
+
editing = true;
|
|
712
|
+
session.select({ id: point.id, start: point.offset, end: point.offset });
|
|
713
|
+
return true;
|
|
714
|
+
}
|
|
364
715
|
function keydown(event) {
|
|
365
716
|
event.stopPropagation();
|
|
366
717
|
if (event.isComposing || composing)
|
|
367
718
|
return;
|
|
368
|
-
if (
|
|
719
|
+
if (focusContextFooter(event))
|
|
720
|
+
return;
|
|
721
|
+
if (event.altKey &&
|
|
722
|
+
!event.shiftKey &&
|
|
723
|
+
!event.ctrlKey &&
|
|
724
|
+
!event.metaKey &&
|
|
725
|
+
event.key === 'ArrowDown') {
|
|
726
|
+
event.preventDefault();
|
|
727
|
+
dismissed = '';
|
|
728
|
+
const result = candidates();
|
|
729
|
+
candidateIndex = Math.max(0, result.candidates.findIndex((item) => item.transformFenceId || item.transformRootId));
|
|
730
|
+
const snapshot = session.getSnapshot();
|
|
731
|
+
const active = textNodes(snapshot.state.document).find((node) => node.id === snapshot.state.caret.id);
|
|
732
|
+
operandMenuKey = `${active.id}:${active.text}:${snapshot.state.caret.start}`;
|
|
733
|
+
drawMenu();
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
if (event.altKey && event.shiftKey && event.code === 'KeyV') {
|
|
737
|
+
event.preventDefault();
|
|
738
|
+
openLatex();
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
if (matrixSelectionKey(event) ||
|
|
742
|
+
keyboardSelect(event) ||
|
|
743
|
+
selectionMenuKey(event) ||
|
|
744
|
+
keyboardCollapse(event))
|
|
369
745
|
return;
|
|
370
746
|
const snapshot = session.getSnapshot(), state = snapshot.state, c = state.caret, active = textNodes(state.document).find((n) => n.id === c.id);
|
|
371
747
|
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'z') {
|
|
@@ -379,7 +755,10 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
379
755
|
if (result.candidates.length && ['ArrowDown', 'ArrowUp'].includes(event.key) && !event.altKey) {
|
|
380
756
|
event.preventDefault();
|
|
381
757
|
const key = `${active.id}:${active.text}:${c.start}`;
|
|
382
|
-
const initialOperandChoice = result.candidates[0]?.wrapOperand
|
|
758
|
+
const initialOperandChoice = (result.candidates[0]?.wrapOperand ||
|
|
759
|
+
result.candidates[0]?.transformRootId ||
|
|
760
|
+
result.candidates[0]?.transformFenceId) &&
|
|
761
|
+
operandMenuKey !== key;
|
|
383
762
|
operandMenuKey = key;
|
|
384
763
|
candidateIndex = initialOperandChoice
|
|
385
764
|
? event.key === 'ArrowDown'
|
|
@@ -392,7 +771,9 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
392
771
|
}
|
|
393
772
|
if (event.key === 'Escape') {
|
|
394
773
|
event.preventDefault();
|
|
395
|
-
if (
|
|
774
|
+
if (dismissSelectionMenu())
|
|
775
|
+
return;
|
|
776
|
+
if (!menu.hidden && result.candidates.length) {
|
|
396
777
|
dismissed = `${active.id}:${active.text}:${c.start}`;
|
|
397
778
|
drawMenu();
|
|
398
779
|
}
|
|
@@ -415,7 +796,9 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
415
796
|
behavior: options.enterBehavior,
|
|
416
797
|
shiftKey: event.shiftKey,
|
|
417
798
|
hasSuggestion: result.candidates.length > 0 &&
|
|
418
|
-
(!result.candidates[0].wrapOperand
|
|
799
|
+
((!result.candidates[0].wrapOperand &&
|
|
800
|
+
!result.candidates[0].transformRootId &&
|
|
801
|
+
!result.candidates[0].transformFenceId) ||
|
|
419
802
|
operandMenuKey === `${active.id}:${active.text}:${c.start}`),
|
|
420
803
|
gridKind: activeGrid(state)?.matrix.type,
|
|
421
804
|
});
|
|
@@ -446,10 +829,23 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
446
829
|
if (event.key === 'Tab' ||
|
|
447
830
|
(!event.shiftKey &&
|
|
448
831
|
c.start === c.end &&
|
|
449
|
-
((event.key === 'ArrowLeft' && c.start ===
|
|
450
|
-
(event.key === 'ArrowRight' && c.end ===
|
|
832
|
+
((event.key === 'ArrowLeft' && c.start === Number(currentInput()?.dataset.tokenStart)) ||
|
|
833
|
+
(event.key === 'ArrowRight' && c.end === Number(currentInput()?.dataset.tokenEnd))))) {
|
|
451
834
|
const direction = event.key === 'Tab' ? (event.shiftKey ? -1 : 1) : event.key === 'ArrowLeft' ? -1 : 1;
|
|
452
|
-
const
|
|
835
|
+
const parts = isLiteralText(state.document, c.id) ? [] : tokenizeMathText(active.text);
|
|
836
|
+
const target = parts[tokenIndexAt(parts, c.start, c.affinity) + direction];
|
|
837
|
+
const offset = target && (direction > 0 ? target.start : target.end);
|
|
838
|
+
const next = target
|
|
839
|
+
? {
|
|
840
|
+
...state,
|
|
841
|
+
caret: {
|
|
842
|
+
id: c.id,
|
|
843
|
+
start: offset,
|
|
844
|
+
end: offset,
|
|
845
|
+
affinity: direction > 0 ? 'forward' : 'backward',
|
|
846
|
+
},
|
|
847
|
+
}
|
|
848
|
+
: moveCaret(state, direction);
|
|
453
849
|
if (next !== state) {
|
|
454
850
|
event.preventDefault();
|
|
455
851
|
session.select(next.caret);
|
|
@@ -462,9 +858,7 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
462
858
|
}
|
|
463
859
|
if (!event.shiftKey && ['ArrowUp', 'ArrowDown'].includes(event.key)) {
|
|
464
860
|
const direction = event.key === 'ArrowUp' ? -1 : 1;
|
|
465
|
-
const next =
|
|
466
|
-
? moveMatrixVertical(state, direction)
|
|
467
|
-
: moveLineVertical(state, direction);
|
|
861
|
+
const next = moveVertical(state, direction, renderedCaretGeometry(surface));
|
|
468
862
|
if (next !== state) {
|
|
469
863
|
event.preventDefault();
|
|
470
864
|
session.select(next.caret);
|
|
@@ -472,6 +866,24 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
472
866
|
return;
|
|
473
867
|
}
|
|
474
868
|
if (c.start === c.end && ['Backspace', 'Delete'].includes(event.key)) {
|
|
869
|
+
const unwrapped = unwrapEmptySlot(state);
|
|
870
|
+
if (unwrapped !== state) {
|
|
871
|
+
event.preventDefault();
|
|
872
|
+
apply(unwrapped);
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
const input = currentInput();
|
|
876
|
+
const left = event.key === 'Backspace';
|
|
877
|
+
if ((left && c.start > 0 && c.start === Number(input?.dataset.tokenStart)) ||
|
|
878
|
+
(!left && c.end < active.text.length && c.end === Number(input?.dataset.tokenEnd))) {
|
|
879
|
+
event.preventDefault();
|
|
880
|
+
const count = left
|
|
881
|
+
? Array.from(active.text.slice(0, c.start)).at(-1).length
|
|
882
|
+
: Array.from(active.text.slice(c.end))[0].length;
|
|
883
|
+
const from = c.start - (left ? count : 0);
|
|
884
|
+
apply(setText(state, active.text.slice(0, from) + active.text.slice(from + count), from));
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
475
887
|
const grid = gridDeletionTarget(state, event.key === 'Backspace' ? -1 : 1);
|
|
476
888
|
if (grid) {
|
|
477
889
|
event.preventDefault();
|
|
@@ -506,9 +918,18 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
506
918
|
if (!best)
|
|
507
919
|
return;
|
|
508
920
|
const mirror = best.querySelector('.me-measure'), value = best.dataset.value;
|
|
921
|
+
// Empty caret slots have no text node for DOM Range to measure.
|
|
922
|
+
if (!value.length || !mirror.firstChild)
|
|
923
|
+
return {
|
|
924
|
+
id: best.dataset.runId,
|
|
925
|
+
offset: Number(best.dataset.tokenStart),
|
|
926
|
+
affinity: 'forward',
|
|
927
|
+
};
|
|
509
928
|
let offset = 0, closest = Infinity;
|
|
510
|
-
|
|
511
|
-
|
|
929
|
+
let i = 0;
|
|
930
|
+
for (const character of ['', ...value]) {
|
|
931
|
+
i += character.length;
|
|
932
|
+
const range = doc.createRange();
|
|
512
933
|
range.setStart(mirror.firstChild, i);
|
|
513
934
|
range.collapse(true);
|
|
514
935
|
const d = Math.abs(range.getBoundingClientRect().left - x);
|
|
@@ -517,70 +938,136 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
517
938
|
offset = i;
|
|
518
939
|
}
|
|
519
940
|
}
|
|
520
|
-
return {
|
|
941
|
+
return {
|
|
942
|
+
id: best.dataset.runId,
|
|
943
|
+
offset: Number(best.dataset.tokenStart) + offset,
|
|
944
|
+
affinity: offset === 0 ? 'forward' : 'backward',
|
|
945
|
+
};
|
|
521
946
|
};
|
|
522
947
|
surface.onpointerdown = (event) => {
|
|
523
|
-
if (event.button || composing || event.target.closest('.me-line-number'))
|
|
524
|
-
return;
|
|
525
|
-
if (event.target instanceof HTMLInputElement)
|
|
948
|
+
if (event.button || composing || drag || event.target.closest('.me-line-number'))
|
|
526
949
|
return;
|
|
527
950
|
const point = pointAt(event.clientX, event.clientY);
|
|
528
951
|
if (point) {
|
|
529
|
-
event.
|
|
952
|
+
event.stopPropagation();
|
|
953
|
+
selectionMenu.reset();
|
|
530
954
|
candidateIndex = 0;
|
|
531
|
-
|
|
532
|
-
|
|
955
|
+
if (event.shiftKey) {
|
|
956
|
+
const snapshot = session.getSnapshot();
|
|
957
|
+
const previous = snapshot.matrixRange;
|
|
958
|
+
const rectangle = matrixRangeBetween(snapshot.state.document, previous
|
|
959
|
+
? focusMatrixCell(snapshot.state, previous.matrixId, previous.anchor).caret.id
|
|
960
|
+
: snapshot.state.caret.id, point.id);
|
|
961
|
+
if (rectangle) {
|
|
962
|
+
event.preventDefault();
|
|
963
|
+
editing = false;
|
|
964
|
+
session.selectMatrixRange(rectangle);
|
|
965
|
+
surface.focus({ preventScroll: true });
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
const input = event.target === currentInput() ? currentInput() : undefined;
|
|
970
|
+
const nativeAnchor = input && event.shiftKey ? nativeSelectionAnchor(input) : null;
|
|
971
|
+
drag = {
|
|
972
|
+
point: nativeAnchor !== null
|
|
973
|
+
? {
|
|
974
|
+
id: input.closest('.me-run').dataset.runId,
|
|
975
|
+
offset: Number(input.dataset.tokenStart) + nativeAnchor,
|
|
976
|
+
}
|
|
977
|
+
: point,
|
|
978
|
+
pointerId: event.pointerId,
|
|
979
|
+
x: event.clientX,
|
|
980
|
+
y: event.clientY,
|
|
981
|
+
moved: false,
|
|
982
|
+
input,
|
|
983
|
+
};
|
|
984
|
+
if (!input) {
|
|
985
|
+
event.preventDefault();
|
|
986
|
+
surface.setPointerCapture(event.pointerId);
|
|
987
|
+
}
|
|
533
988
|
}
|
|
534
989
|
};
|
|
535
|
-
|
|
536
|
-
if (
|
|
990
|
+
const movePointer = (event) => {
|
|
991
|
+
if (composing ||
|
|
992
|
+
!drag ||
|
|
993
|
+
!extendsMathSelection(drag, event, drag.input?.getBoundingClientRect()))
|
|
537
994
|
return;
|
|
538
995
|
const point = pointAt(event.clientX, event.clientY);
|
|
539
996
|
if (point) {
|
|
997
|
+
event.preventDefault();
|
|
998
|
+
event.stopPropagation();
|
|
540
999
|
drag.moved = true;
|
|
1000
|
+
// Transfer capture before rendering removes the native input that began this drag.
|
|
1001
|
+
surface.setPointerCapture(event.pointerId);
|
|
541
1002
|
editing = false;
|
|
542
|
-
|
|
543
|
-
|
|
1003
|
+
selectedGrid = undefined;
|
|
1004
|
+
const snapshot = session.getSnapshot();
|
|
1005
|
+
const rectangle = dragMatrix ?? matrixRangeBetween(snapshot.state.document, drag.point.id, point.id);
|
|
1006
|
+
const bounds = rectangle && resolveMatrixRange(snapshot.state.document, rectangle);
|
|
1007
|
+
if (rectangle && bounds) {
|
|
1008
|
+
dragMatrix = matrixPointerRange(surface, rectangle, bounds.matrix.columns, event.clientX, event.clientY);
|
|
1009
|
+
session.selectMatrixRange(dragMatrix);
|
|
1010
|
+
}
|
|
1011
|
+
else
|
|
1012
|
+
session.selectRange({ anchor: drag.point, focus: point });
|
|
1013
|
+
surface.focus({ preventScroll: true });
|
|
544
1014
|
}
|
|
545
1015
|
};
|
|
546
|
-
|
|
1016
|
+
const endPointer = (event) => {
|
|
547
1017
|
const previous = drag;
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
if (previous
|
|
1018
|
+
if (!previous || previous.pointerId !== event.pointerId)
|
|
1019
|
+
return;
|
|
1020
|
+
cancelDrag();
|
|
1021
|
+
if (previous.moved) {
|
|
1022
|
+
event.preventDefault();
|
|
1023
|
+
event.stopPropagation();
|
|
1024
|
+
}
|
|
1025
|
+
else if (!previous.input) {
|
|
552
1026
|
editing = true;
|
|
553
1027
|
session.select({
|
|
554
1028
|
id: previous.point.id,
|
|
555
1029
|
start: previous.point.offset,
|
|
556
1030
|
end: previous.point.offset,
|
|
1031
|
+
affinity: previous.point.affinity,
|
|
557
1032
|
});
|
|
558
1033
|
}
|
|
559
1034
|
};
|
|
560
|
-
|
|
561
|
-
drag
|
|
1035
|
+
const cancelPointer = (event) => {
|
|
1036
|
+
if (drag?.pointerId === event.pointerId)
|
|
1037
|
+
cancelDrag();
|
|
1038
|
+
};
|
|
1039
|
+
surface.onlostpointercapture = (event) => {
|
|
1040
|
+
if (event.target === surface)
|
|
1041
|
+
cancelPointer(event);
|
|
1042
|
+
};
|
|
1043
|
+
surface.ondragstart = (event) => {
|
|
1044
|
+
// A formula drag selects model content; it must not become host HTML drag-and-drop.
|
|
1045
|
+
event.preventDefault();
|
|
1046
|
+
event.stopPropagation();
|
|
562
1047
|
};
|
|
1048
|
+
doc.addEventListener('pointermove', movePointer, true);
|
|
1049
|
+
doc.addEventListener('pointerup', endPointer, true);
|
|
1050
|
+
doc.addEventListener('pointercancel', cancelPointer, true);
|
|
1051
|
+
win.addEventListener('blur', cancelDrag);
|
|
563
1052
|
surface.onkeydown = (event) => {
|
|
564
|
-
if (event.target
|
|
1053
|
+
if (event.target === currentInput())
|
|
565
1054
|
return;
|
|
566
1055
|
event.stopPropagation();
|
|
567
|
-
if (
|
|
1056
|
+
if (composing || event.isComposing)
|
|
1057
|
+
return;
|
|
1058
|
+
if (focusContextFooter(event))
|
|
1059
|
+
return;
|
|
1060
|
+
if (event.altKey && event.shiftKey && event.code === 'KeyV') {
|
|
1061
|
+
event.preventDefault();
|
|
1062
|
+
openLatex();
|
|
568
1063
|
return;
|
|
569
|
-
const snapshot = session.getSnapshot();
|
|
570
|
-
if (snapshot.range && ['ArrowDown', 'ArrowUp', 'Enter'].includes(event.key)) {
|
|
571
|
-
const items = [...menu.querySelectorAll('[role="option"]')];
|
|
572
|
-
if (items.length) {
|
|
573
|
-
event.preventDefault();
|
|
574
|
-
if (event.key === 'Enter')
|
|
575
|
-
items[candidateIndex % items.length].click();
|
|
576
|
-
else {
|
|
577
|
-
candidateIndex =
|
|
578
|
-
(candidateIndex + (event.key === 'ArrowDown' ? 1 : items.length - 1)) % items.length;
|
|
579
|
-
drawMenu();
|
|
580
|
-
}
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
1064
|
}
|
|
1065
|
+
if (matrixSelectionKey(event) ||
|
|
1066
|
+
keyboardSelect(event) ||
|
|
1067
|
+
selectionMenuKey(event) ||
|
|
1068
|
+
keyboardCollapse(event))
|
|
1069
|
+
return;
|
|
1070
|
+
const snapshot = session.getSnapshot();
|
|
584
1071
|
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'a') {
|
|
585
1072
|
event.preventDefault();
|
|
586
1073
|
const nodes = textNodes(snapshot.state.document);
|
|
@@ -594,7 +1081,11 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
594
1081
|
perform({ type: event.shiftKey ? 'redo' : 'undo' });
|
|
595
1082
|
}
|
|
596
1083
|
else if (event.key === 'Escape') {
|
|
1084
|
+
event.preventDefault();
|
|
1085
|
+
if (dismissSelectionMenu())
|
|
1086
|
+
return;
|
|
597
1087
|
session.selectRange();
|
|
1088
|
+
options.onCancel?.();
|
|
598
1089
|
}
|
|
599
1090
|
else if (snapshot.range &&
|
|
600
1091
|
!event.metaKey &&
|
|
@@ -617,12 +1108,17 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
617
1108
|
}
|
|
618
1109
|
};
|
|
619
1110
|
const copy = (event, cut = false) => {
|
|
1111
|
+
if (composing || !event.clipboardData)
|
|
1112
|
+
return;
|
|
620
1113
|
const fragment = session.copy();
|
|
621
1114
|
if (!fragment)
|
|
622
1115
|
return;
|
|
623
1116
|
event.preventDefault();
|
|
624
1117
|
event.stopPropagation();
|
|
625
|
-
|
|
1118
|
+
const matrix = session.copyMatrix();
|
|
1119
|
+
if (matrix)
|
|
1120
|
+
event.clipboardData.setData(MATH_MATRIX_CLIPBOARD_TYPE, JSON.stringify(matrix));
|
|
1121
|
+
event.clipboardData.setData(MATH_CLIPBOARD_TYPE, JSON.stringify(fragment));
|
|
626
1122
|
event.clipboardData?.setData('text/plain', toLatex(fragmentLatex(fragment)));
|
|
627
1123
|
if (cut)
|
|
628
1124
|
perform({ type: 'text', value: '' });
|
|
@@ -633,6 +1129,23 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
633
1129
|
if (composing)
|
|
634
1130
|
return;
|
|
635
1131
|
const data = event.clipboardData, fragment = parseFragment(data?.getData(MATH_CLIPBOARD_TYPE) ?? ''), text = data?.getData('text/plain') ?? '';
|
|
1132
|
+
const matrixSource = data?.getData(MATH_MATRIX_CLIPBOARD_TYPE) ?? '';
|
|
1133
|
+
const matrix = matrixSource ? parseMatrixFragment(matrixSource) : matrixFromTSV(text);
|
|
1134
|
+
const snapshot = session.getSnapshot();
|
|
1135
|
+
const inMatrix = !!snapshot.matrixRange || !!activeMatrix(snapshot.state);
|
|
1136
|
+
if (matrixSource || (inMatrix && text.includes('\t'))) {
|
|
1137
|
+
event.preventDefault();
|
|
1138
|
+
event.stopPropagation();
|
|
1139
|
+
if (!matrix) {
|
|
1140
|
+
notify('matrix.pasteRejected');
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
editing = true;
|
|
1144
|
+
const ok = inMatrix ? session.pasteMatrix(matrix) : session.paste(matrixFragment(matrix));
|
|
1145
|
+
if (!ok)
|
|
1146
|
+
notify('matrix.pasteRejected');
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
636
1149
|
if (!fragment && !text)
|
|
637
1150
|
return;
|
|
638
1151
|
event.preventDefault();
|
|
@@ -647,6 +1160,7 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
647
1160
|
!composing &&
|
|
648
1161
|
!root.contains(event.relatedTarget) &&
|
|
649
1162
|
!menu.contains(event.relatedTarget)) {
|
|
1163
|
+
cancelDrag();
|
|
650
1164
|
editing = false;
|
|
651
1165
|
render();
|
|
652
1166
|
}
|
|
@@ -659,24 +1173,34 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
659
1173
|
? mountMathToolbar(tools, session, {
|
|
660
1174
|
kinds: Array.isArray(enabled) ? enabled : undefined,
|
|
661
1175
|
maxItems: options.toolbarMaxItems,
|
|
1176
|
+
onPasteLatex: openLatex,
|
|
1177
|
+
onOpenPanel: () => {
|
|
1178
|
+
latexPanel?.destroy();
|
|
1179
|
+
latexPanel = undefined;
|
|
1180
|
+
},
|
|
662
1181
|
onExecute: () => {
|
|
663
1182
|
editing = true;
|
|
664
1183
|
render();
|
|
1184
|
+
if (session.getSnapshot().matrixRange)
|
|
1185
|
+
surface.focus();
|
|
665
1186
|
},
|
|
666
1187
|
})
|
|
667
1188
|
: undefined;
|
|
1189
|
+
toolbar?.setDisabled(composing);
|
|
668
1190
|
};
|
|
669
1191
|
const unsubscribe = session.subscribe((snapshot, changed) => {
|
|
1192
|
+
if (changed) {
|
|
1193
|
+
cancelDrag();
|
|
1194
|
+
selectionMenu.reset();
|
|
1195
|
+
}
|
|
670
1196
|
render();
|
|
671
1197
|
if (changed)
|
|
672
1198
|
options.onChange?.(snapshot.state.document, snapshot.latex);
|
|
673
1199
|
});
|
|
674
|
-
const
|
|
1200
|
+
const stopPosition = followMathMenuPosition(root, () => {
|
|
675
1201
|
if (!menu.hidden)
|
|
676
1202
|
drawMenu();
|
|
677
|
-
};
|
|
678
|
-
window.addEventListener('resize', reposition);
|
|
679
|
-
window.addEventListener('scroll', reposition, true);
|
|
1203
|
+
}, menu);
|
|
680
1204
|
rebuildToolbar();
|
|
681
1205
|
render();
|
|
682
1206
|
return {
|
|
@@ -684,6 +1208,8 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
684
1208
|
focus() {
|
|
685
1209
|
editing = true;
|
|
686
1210
|
render();
|
|
1211
|
+
if (session.getSnapshot().matrixRange)
|
|
1212
|
+
surface.focus();
|
|
687
1213
|
},
|
|
688
1214
|
update(next) {
|
|
689
1215
|
options = { ...options, ...next };
|
|
@@ -695,11 +1221,17 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
695
1221
|
if (destroyed)
|
|
696
1222
|
return;
|
|
697
1223
|
destroyed = true;
|
|
1224
|
+
cancelDrag();
|
|
698
1225
|
unsubscribe();
|
|
1226
|
+
latexPanel?.destroy();
|
|
699
1227
|
toolbar?.destroy();
|
|
700
1228
|
root.removeEventListener('focusout', blur);
|
|
701
|
-
|
|
702
|
-
|
|
1229
|
+
stopPosition();
|
|
1230
|
+
doc.removeEventListener('pointermove', movePointer, true);
|
|
1231
|
+
doc.removeEventListener('pointerup', endPointer, true);
|
|
1232
|
+
doc.removeEventListener('pointercancel', cancelPointer, true);
|
|
1233
|
+
win.removeEventListener('blur', cancelDrag);
|
|
1234
|
+
stopTheme();
|
|
703
1235
|
menu.remove();
|
|
704
1236
|
root.remove();
|
|
705
1237
|
if (ownsSession)
|
|
@@ -707,4 +1239,3 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
707
1239
|
},
|
|
708
1240
|
};
|
|
709
1241
|
}
|
|
710
|
-
//# sourceMappingURL=dom.js.map
|