@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
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, findSuggestions, mathStructures } from './suggestions.js';
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 = findSuggestions(active.text, state.caret.start, snapshot.locale);
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) || keyboardSelect(event) || keyboardCollapse(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 && operandMenuKey !== key;
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) || keyboardSelect(event) || keyboardCollapse(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 reposition = (event) => {
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
- win.removeEventListener('resize', reposition);
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('{}'))
@@ -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
  };
@@ -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
  }
@@ -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": ["사인"],
@@ -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": ["사인"],
@@ -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;