@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.
- package/API-SESSION.md +10 -0
- package/API-WEB-COMPONENT.md +2 -0
- package/CHANGELOG.md +61 -0
- package/EDITING-SCENARIOS.md +174 -0
- package/IMPLEMENTATION.md +95 -0
- package/LATEX-GUIDE.md +22 -0
- package/LATEX-MODEL.md +12 -0
- package/LATEX-SCOPE.md +10 -1
- package/README.md +73 -19
- package/RELEASING.md +81 -10
- package/RENDERING-TESTS.md +79 -0
- package/ROADMAP.md +132 -5
- package/VALIDATION.md +331 -0
- package/dist/context-tools.d.ts +21 -0
- package/dist/context-tools.js +37 -0
- package/dist/dom/context-keyboard.d.ts +2 -0
- package/dist/dom/context-keyboard.js +22 -0
- package/dist/dom/menu-position.d.ts +3 -0
- package/dist/dom/menu-position.js +45 -1
- package/dist/dom.d.ts +2 -0
- package/dist/dom.js +187 -38
- package/dist/latex.js +11 -0
- package/dist/locales/en.js +13 -1
- package/dist/locales/en.json +13 -1
- package/dist/locales/ko.js +13 -1
- package/dist/locales/ko.json +13 -1
- package/dist/math-editor.d.ts +3 -1
- package/dist/math-editor.js +394 -280
- 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/model.d.ts +4 -0
- package/dist/model.js +49 -4
- package/dist/root-transform.d.ts +19 -0
- package/dist/root-transform.js +69 -0
- package/dist/suggestions.d.ts +9 -0
- package/dist/suggestions.js +53 -0
- package/dist/web-component.js +9 -1
- 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 +695 -44
package/dist/dom.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
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';
|
|
1
6
|
import { mountLatexPanel } from './dom/latex-panel.js';
|
|
2
7
|
import { followMathTheme, syncMathTheme } from './dom/theme.js';
|
|
3
8
|
import { matrixPointerRange } from './dom/matrix-selection.js';
|
|
4
|
-
import { positionMathMenu } from './dom/menu-position.js';
|
|
5
|
-
import { fences } from './fences.js';
|
|
9
|
+
import { positionMathMenu, followMathMenuPosition } from './dom/menu-position.js';
|
|
10
|
+
import { fences, fencePairs } from './fences.js';
|
|
6
11
|
import { createEditorLabels } from './editor-labels.js';
|
|
7
12
|
import { mathEnterAction } from './enter-policy.js';
|
|
8
13
|
export { mountMathLatex, mountMathPreview, } from './outputs.js';
|
|
@@ -11,14 +16,14 @@ import { mountMathToolbar } from './dom/toolbar.js';
|
|
|
11
16
|
export { mountMathToolbar } from './dom/toolbar.js';
|
|
12
17
|
import { createMathSession, } from './session.js';
|
|
13
18
|
import { translate, mathLocaleDirection } from './i18n.js';
|
|
14
|
-
import { documentRows, isLiteralText, textNodes, setText, moveCaret, unwrapPrevious, gridDeletionTarget, removeGrid, isGrid, toLatex, } from './model.js';
|
|
19
|
+
import { documentRows, isLiteralText, textNodes, setText, moveCaret, unwrapPrevious, unwrapEmptySlot, gridDeletionTarget, removeGrid, isGrid, toLatex, } from './model.js';
|
|
15
20
|
import { activeGrid, activeMatrix, resizeMatrix } from './matrix.js';
|
|
16
21
|
import { MATH_MATRIX_CLIPBOARD_TYPE, parseMatrixFragment, matrixFragment, matrixFromTSV, matrixRangeBetween, resolveMatrixRange, extendMatrixRange, focusMatrixCell, } from './matrix-range.js';
|
|
17
22
|
import { joinPreviousLine } from './lines.js';
|
|
18
23
|
import { moveVertical } from './vertical-navigation.js';
|
|
19
24
|
import { renderedCaretGeometry } from './dom/caret-geometry.js';
|
|
20
25
|
import { extendsMathSelection, nativeSelectionAnchor, createSelectionMenuState, textSelectionBounds, } from './dom/selection.js';
|
|
21
|
-
import { acceptSuggestion,
|
|
26
|
+
import { acceptSuggestion, findStateSuggestions, mathStructures } from './suggestions.js';
|
|
22
27
|
import { tokenizeMathText, tokenIndexAt } from './tokens.js';
|
|
23
28
|
import { fragmentLatex, parseFragment, MATH_CLIPBOARD_TYPE, extendKeyboardRange, collapseKeyboardRange, resolveRange, copyRange, wrappingKinds, } from './range.js';
|
|
24
29
|
/** Native DOM renderer. No React or framework runtime is imported by this module. */
|
|
@@ -37,8 +42,8 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
37
42
|
const ownsSession = !initial.session;
|
|
38
43
|
if (initial.session)
|
|
39
44
|
session.configure({ locale: initial.locale, mode: initial.mode });
|
|
40
|
-
const root = element('span', 'me-editor me-dom-editor'), tools = element('span'), surface = element('span', 'me-surface'), status = element('span', 'me-dom-status');
|
|
41
|
-
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);
|
|
42
47
|
host.append(root);
|
|
43
48
|
surface.tabIndex = 0;
|
|
44
49
|
surface.setAttribute('role', 'group');
|
|
@@ -96,7 +101,7 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
96
101
|
};
|
|
97
102
|
const candidates = () => {
|
|
98
103
|
const snapshot = session.getSnapshot(), state = snapshot.state, active = textNodes(state.document).find((n) => n.id === state.caret.id);
|
|
99
|
-
const result =
|
|
104
|
+
const result = findStateSuggestions(state, snapshot.locale);
|
|
100
105
|
if (!editing ||
|
|
101
106
|
isLiteralText(state.document, state.caret.id) ||
|
|
102
107
|
snapshot.range ||
|
|
@@ -262,6 +267,90 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
262
267
|
event.stopPropagation();
|
|
263
268
|
(currentInput() ?? surface).focus();
|
|
264
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
|
+
};
|
|
265
354
|
const render = () => {
|
|
266
355
|
if (destroyed || composing || rendering)
|
|
267
356
|
return;
|
|
@@ -288,8 +377,11 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
288
377
|
status.textContent = '';
|
|
289
378
|
const selected = (r) => spans?.find((span) => span.row.id === r.id);
|
|
290
379
|
const labels = createEditorLabels(snapshot.locale);
|
|
380
|
+
const layout = mathLayout(state.document, snapshot.mode);
|
|
291
381
|
const renderRow = (r, name, inheritedSelection = false) => {
|
|
292
382
|
const view = element('span', 'me-row');
|
|
383
|
+
const spacing = mathRunSpacing(r);
|
|
384
|
+
view.dataset.layoutStyle = layout.rows.get(r.id);
|
|
293
385
|
const selection = selected(r);
|
|
294
386
|
let offset = 0;
|
|
295
387
|
for (const child of r.children) {
|
|
@@ -311,6 +403,14 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
311
403
|
run.dataset.runId = child.id;
|
|
312
404
|
run.dataset.value = token.text;
|
|
313
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
|
+
}
|
|
314
414
|
run.dataset.tokenStart = String(token.start);
|
|
315
415
|
run.dataset.tokenEnd = String(token.end);
|
|
316
416
|
const mirror = element('span', 'me-measure', token.text || (boundary ? '' : '□'));
|
|
@@ -366,6 +466,7 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
366
466
|
cancelDrag();
|
|
367
467
|
composing = true;
|
|
368
468
|
toolbar?.setDisabled(true);
|
|
469
|
+
renderContextFooter();
|
|
369
470
|
menu.hidden = true;
|
|
370
471
|
});
|
|
371
472
|
input.addEventListener('compositionend', () => {
|
|
@@ -424,6 +525,19 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
424
525
|
}
|
|
425
526
|
const structure = element('span', `me-structure me-${child.type}`);
|
|
426
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
|
+
}
|
|
427
541
|
if (child.mathStyle)
|
|
428
542
|
structure.dataset.mathStyle = child.mathStyle;
|
|
429
543
|
if (['limit', 'limsup', 'liminf'].includes(child.type)) {
|
|
@@ -507,6 +621,7 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
507
621
|
const start = Number(input.dataset.tokenStart);
|
|
508
622
|
input.setSelectionRange(state.caret.start - start, state.caret.end - start);
|
|
509
623
|
}
|
|
624
|
+
renderContextFooter();
|
|
510
625
|
rendering = false;
|
|
511
626
|
drawMenu();
|
|
512
627
|
};
|
|
@@ -563,6 +678,28 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
563
678
|
surface.focus();
|
|
564
679
|
return true;
|
|
565
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
|
+
}
|
|
566
703
|
function keyboardCollapse(event) {
|
|
567
704
|
if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey)
|
|
568
705
|
return false;
|
|
@@ -579,12 +716,32 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
579
716
|
event.stopPropagation();
|
|
580
717
|
if (event.isComposing || composing)
|
|
581
718
|
return;
|
|
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
|
+
}
|
|
582
736
|
if (event.altKey && event.shiftKey && event.code === 'KeyV') {
|
|
583
737
|
event.preventDefault();
|
|
584
738
|
openLatex();
|
|
585
739
|
return;
|
|
586
740
|
}
|
|
587
|
-
if (matrixSelectionKey(event) ||
|
|
741
|
+
if (matrixSelectionKey(event) ||
|
|
742
|
+
keyboardSelect(event) ||
|
|
743
|
+
selectionMenuKey(event) ||
|
|
744
|
+
keyboardCollapse(event))
|
|
588
745
|
return;
|
|
589
746
|
const snapshot = session.getSnapshot(), state = snapshot.state, c = state.caret, active = textNodes(state.document).find((n) => n.id === c.id);
|
|
590
747
|
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'z') {
|
|
@@ -598,7 +755,10 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
598
755
|
if (result.candidates.length && ['ArrowDown', 'ArrowUp'].includes(event.key) && !event.altKey) {
|
|
599
756
|
event.preventDefault();
|
|
600
757
|
const key = `${active.id}:${active.text}:${c.start}`;
|
|
601
|
-
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;
|
|
602
762
|
operandMenuKey = key;
|
|
603
763
|
candidateIndex = initialOperandChoice
|
|
604
764
|
? event.key === 'ArrowDown'
|
|
@@ -636,7 +796,9 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
636
796
|
behavior: options.enterBehavior,
|
|
637
797
|
shiftKey: event.shiftKey,
|
|
638
798
|
hasSuggestion: result.candidates.length > 0 &&
|
|
639
|
-
(!result.candidates[0].wrapOperand
|
|
799
|
+
((!result.candidates[0].wrapOperand &&
|
|
800
|
+
!result.candidates[0].transformRootId &&
|
|
801
|
+
!result.candidates[0].transformFenceId) ||
|
|
640
802
|
operandMenuKey === `${active.id}:${active.text}:${c.start}`),
|
|
641
803
|
gridKind: activeGrid(state)?.matrix.type,
|
|
642
804
|
});
|
|
@@ -704,6 +866,12 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
704
866
|
return;
|
|
705
867
|
}
|
|
706
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
|
+
}
|
|
707
875
|
const input = currentInput();
|
|
708
876
|
const left = event.key === 'Backspace';
|
|
709
877
|
if ((left && c.start > 0 && c.start === Number(input?.dataset.tokenStart)) ||
|
|
@@ -887,32 +1055,19 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
887
1055
|
event.stopPropagation();
|
|
888
1056
|
if (composing || event.isComposing)
|
|
889
1057
|
return;
|
|
1058
|
+
if (focusContextFooter(event))
|
|
1059
|
+
return;
|
|
890
1060
|
if (event.altKey && event.shiftKey && event.code === 'KeyV') {
|
|
891
1061
|
event.preventDefault();
|
|
892
1062
|
openLatex();
|
|
893
1063
|
return;
|
|
894
1064
|
}
|
|
895
|
-
if (matrixSelectionKey(event) ||
|
|
1065
|
+
if (matrixSelectionKey(event) ||
|
|
1066
|
+
keyboardSelect(event) ||
|
|
1067
|
+
selectionMenuKey(event) ||
|
|
1068
|
+
keyboardCollapse(event))
|
|
896
1069
|
return;
|
|
897
1070
|
const snapshot = session.getSnapshot();
|
|
898
|
-
if (snapshot.range &&
|
|
899
|
-
!event.ctrlKey &&
|
|
900
|
-
!event.metaKey &&
|
|
901
|
-
!event.shiftKey &&
|
|
902
|
-
(event.key === 'Enter' || (event.altKey && ['ArrowDown', 'ArrowUp'].includes(event.key)))) {
|
|
903
|
-
const items = [...menu.querySelectorAll('[role="option"]')];
|
|
904
|
-
if (items.length) {
|
|
905
|
-
event.preventDefault();
|
|
906
|
-
if (event.key === 'Enter')
|
|
907
|
-
items[candidateIndex % items.length].click();
|
|
908
|
-
else {
|
|
909
|
-
candidateIndex =
|
|
910
|
-
(candidateIndex + (event.key === 'ArrowDown' ? 1 : items.length - 1)) % items.length;
|
|
911
|
-
drawMenu();
|
|
912
|
-
}
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
1071
|
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'a') {
|
|
917
1072
|
event.preventDefault();
|
|
918
1073
|
const nodes = textNodes(snapshot.state.document);
|
|
@@ -1042,15 +1197,10 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
1042
1197
|
if (changed)
|
|
1043
1198
|
options.onChange?.(snapshot.state.document, snapshot.latex);
|
|
1044
1199
|
});
|
|
1045
|
-
const
|
|
1046
|
-
// Scrolling the candidate list must not rebuild it and reset its scroll position.
|
|
1047
|
-
if (event.target && 'nodeType' in event.target && menu.contains(event.target))
|
|
1048
|
-
return;
|
|
1200
|
+
const stopPosition = followMathMenuPosition(root, () => {
|
|
1049
1201
|
if (!menu.hidden)
|
|
1050
1202
|
drawMenu();
|
|
1051
|
-
};
|
|
1052
|
-
win.addEventListener('resize', reposition);
|
|
1053
|
-
win.addEventListener('scroll', reposition, true);
|
|
1203
|
+
}, menu);
|
|
1054
1204
|
rebuildToolbar();
|
|
1055
1205
|
render();
|
|
1056
1206
|
return {
|
|
@@ -1076,8 +1226,7 @@ export function mountMathEditor(host, initial = {}) {
|
|
|
1076
1226
|
latexPanel?.destroy();
|
|
1077
1227
|
toolbar?.destroy();
|
|
1078
1228
|
root.removeEventListener('focusout', blur);
|
|
1079
|
-
|
|
1080
|
-
win.removeEventListener('scroll', reposition, true);
|
|
1229
|
+
stopPosition();
|
|
1081
1230
|
doc.removeEventListener('pointermove', movePointer, true);
|
|
1082
1231
|
doc.removeEventListener('pointerup', endPointer, true);
|
|
1083
1232
|
doc.removeEventListener('pointercancel', cancelPointer, true);
|
package/dist/latex.js
CHANGED
|
@@ -391,6 +391,17 @@ class LatexParser {
|
|
|
391
391
|
return row(command);
|
|
392
392
|
if (command === 'backslash')
|
|
393
393
|
return row('\\');
|
|
394
|
+
if (command === 'char') {
|
|
395
|
+
// Canonical math-mode literal caret/tilde output. This is intentionally
|
|
396
|
+
// bounded; arbitrary TeX character codes are not part of the model API.
|
|
397
|
+
this.skip();
|
|
398
|
+
const match = this.source.slice(this.at, this.end).match(/^"([0-9a-f]+)/i);
|
|
399
|
+
const code = match ? Number.parseInt(match[1], 16) : NaN;
|
|
400
|
+
if (code !== 0x5e && code !== 0x7e)
|
|
401
|
+
this.fail('unsupported-command', 'Only literal caret and tilde character codes are supported.', start);
|
|
402
|
+
this.at += match[0].length;
|
|
403
|
+
return row(String.fromCodePoint(code));
|
|
404
|
+
}
|
|
394
405
|
if (command === 'textasciicircum' || command === 'textasciitilde') {
|
|
395
406
|
this.skip();
|
|
396
407
|
if (!this.starts('{}'))
|
package/dist/locales/en.js
CHANGED
|
@@ -341,7 +341,19 @@ export default {
|
|
|
341
341
|
"presentation.display": "Display size",
|
|
342
342
|
"presentation.text": "Text size",
|
|
343
343
|
"presentation.aboveBelow": "Above and below",
|
|
344
|
-
"presentation.side": "Beside operator"
|
|
344
|
+
"presentation.side": "Beside operator",
|
|
345
|
+
"suggestion.transform": "Change to {kind}",
|
|
346
|
+
"suggestion.transformRootDetail": "Keep the radicand. A square root requires an empty index or 2.",
|
|
347
|
+
"context.label": "Structure tools",
|
|
348
|
+
"context.current": "Editing: {kind}",
|
|
349
|
+
"context.editIndex": "Edit index",
|
|
350
|
+
"context.keyboardHint": "F6: tools · Esc: back to input",
|
|
351
|
+
"context.squareIndexRequired": "Change the index to 2 or clear it to use a square root.",
|
|
352
|
+
"context.root": "Square root",
|
|
353
|
+
"context.indexedRoot": "Indexed root",
|
|
354
|
+
"context.fenceKeyboardHint": "F6: tools · ← →: choose · Enter: apply · Esc: back to input",
|
|
355
|
+
"suggestion.transformFence": "Change brackets: {kind}",
|
|
356
|
+
"suggestion.transformFenceDetail": "Keep the enclosed formula and cursor position"
|
|
345
357
|
},
|
|
346
358
|
"aliases": {}
|
|
347
359
|
};
|
package/dist/locales/en.json
CHANGED
|
@@ -340,7 +340,19 @@
|
|
|
340
340
|
"presentation.display": "Display size",
|
|
341
341
|
"presentation.text": "Text size",
|
|
342
342
|
"presentation.aboveBelow": "Above and below",
|
|
343
|
-
"presentation.side": "Beside operator"
|
|
343
|
+
"presentation.side": "Beside operator",
|
|
344
|
+
"suggestion.transform": "Change to {kind}",
|
|
345
|
+
"suggestion.transformRootDetail": "Keep the radicand. A square root requires an empty index or 2.",
|
|
346
|
+
"context.label": "Structure tools",
|
|
347
|
+
"context.current": "Editing: {kind}",
|
|
348
|
+
"context.editIndex": "Edit index",
|
|
349
|
+
"context.keyboardHint": "F6: tools · Esc: back to input",
|
|
350
|
+
"context.squareIndexRequired": "Change the index to 2 or clear it to use a square root.",
|
|
351
|
+
"context.root": "Square root",
|
|
352
|
+
"context.indexedRoot": "Indexed root",
|
|
353
|
+
"context.fenceKeyboardHint": "F6: tools · ← →: choose · Enter: apply · Esc: back to input",
|
|
354
|
+
"suggestion.transformFence": "Change brackets: {kind}",
|
|
355
|
+
"suggestion.transformFenceDetail": "Keep the enclosed formula and cursor position"
|
|
344
356
|
},
|
|
345
357
|
"aliases": {}
|
|
346
358
|
}
|
package/dist/locales/ko.js
CHANGED
|
@@ -341,7 +341,19 @@ export default {
|
|
|
341
341
|
"presentation.display": "큰 표시",
|
|
342
342
|
"presentation.text": "작은 표시",
|
|
343
343
|
"presentation.aboveBelow": "위·아래",
|
|
344
|
-
"presentation.side": "오른쪽"
|
|
344
|
+
"presentation.side": "오른쪽",
|
|
345
|
+
"suggestion.transform": "{kind}으로 변형",
|
|
346
|
+
"suggestion.transformRootDetail": "루트 안의 수식을 유지합니다. 제곱근으로 바꾸려면 차수가 2이거나 비어 있어야 합니다.",
|
|
347
|
+
"context.label": "구조 편집 도구",
|
|
348
|
+
"context.current": "현재 구조: {kind}",
|
|
349
|
+
"context.editIndex": "차수 편집",
|
|
350
|
+
"context.keyboardHint": "F6: 도구로 이동 · Esc: 입력으로",
|
|
351
|
+
"context.squareIndexRequired": "제곱근으로 바꾸려면 차수를 2로 바꾸거나 비워 주세요.",
|
|
352
|
+
"context.root": "제곱근",
|
|
353
|
+
"context.indexedRoot": "n제곱근",
|
|
354
|
+
"context.fenceKeyboardHint": "F6: 도구로 이동 · ← →: 선택 · Enter: 적용 · Esc: 입력으로",
|
|
355
|
+
"suggestion.transformFence": "괄호 변경: {kind}",
|
|
356
|
+
"suggestion.transformFenceDetail": "안의 수식과 커서 위치를 유지합니다"
|
|
345
357
|
},
|
|
346
358
|
"aliases": {
|
|
347
359
|
"function-sin": ["사인"],
|
package/dist/locales/ko.json
CHANGED
|
@@ -340,7 +340,19 @@
|
|
|
340
340
|
"presentation.display": "큰 표시",
|
|
341
341
|
"presentation.text": "작은 표시",
|
|
342
342
|
"presentation.aboveBelow": "위·아래",
|
|
343
|
-
"presentation.side": "오른쪽"
|
|
343
|
+
"presentation.side": "오른쪽",
|
|
344
|
+
"suggestion.transform": "{kind}으로 변형",
|
|
345
|
+
"suggestion.transformRootDetail": "루트 안의 수식을 유지합니다. 제곱근으로 바꾸려면 차수가 2이거나 비어 있어야 합니다.",
|
|
346
|
+
"context.label": "구조 편집 도구",
|
|
347
|
+
"context.current": "현재 구조: {kind}",
|
|
348
|
+
"context.editIndex": "차수 편집",
|
|
349
|
+
"context.keyboardHint": "F6: 도구로 이동 · Esc: 입력으로",
|
|
350
|
+
"context.squareIndexRequired": "제곱근으로 바꾸려면 차수를 2로 바꾸거나 비워 주세요.",
|
|
351
|
+
"context.root": "제곱근",
|
|
352
|
+
"context.indexedRoot": "n제곱근",
|
|
353
|
+
"context.fenceKeyboardHint": "F6: 도구로 이동 · ← →: 선택 · Enter: 적용 · Esc: 입력으로",
|
|
354
|
+
"suggestion.transformFence": "괄호 변경: {kind}",
|
|
355
|
+
"suggestion.transformFenceDetail": "안의 수식과 커서 위치를 유지합니다"
|
|
344
356
|
},
|
|
345
357
|
"aliases": {
|
|
346
358
|
"function-sin": ["사인"],
|
package/dist/math-editor.d.ts
CHANGED
|
@@ -28,6 +28,8 @@ export interface MathEditorProps {
|
|
|
28
28
|
toolbar?: boolean | readonly StructureKind[];
|
|
29
29
|
/** Visible structure buttons before More; defaults to 8. */
|
|
30
30
|
toolbarMaxItems?: number;
|
|
31
|
+
/** Show contextual structure actions below the editing surface. Defaults to true. */
|
|
32
|
+
contextTools?: boolean;
|
|
31
33
|
/** Host-owned actions rendered at the end of the main toolbar. */
|
|
32
34
|
toolbarEnd?: ReactNode;
|
|
33
35
|
showTokenLegend?: boolean;
|
|
@@ -43,4 +45,4 @@ export interface MathEditorProps {
|
|
|
43
45
|
label?: string;
|
|
44
46
|
}
|
|
45
47
|
/** Native text fields own IME and local text selection; the tree owns structural editing. */
|
|
46
|
-
export declare function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus, enterBehavior, onCommit, onCancel, showPopovers, defaultValue, excludedStructures, multiline, onChange, onExit, label, locale, showLineNumbers, toolbar, toolbarMaxItems, toolbarEnd, showTokenLegend, }: MathEditorProps): import("react/jsx-runtime").JSX.Element;
|
|
48
|
+
export declare function MathEditor({ preferences: suppliedPreferences, apiRef, autoFocus, enterBehavior, onCommit, onCancel, showPopovers, defaultValue, excludedStructures, multiline, onChange, onExit, label, locale, showLineNumbers, toolbar, toolbarMaxItems, contextTools, toolbarEnd, showTokenLegend, }: MathEditorProps): import("react/jsx-runtime").JSX.Element;
|