@barocss/math-editor 0.4.0 → 0.6.1

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 (76) hide show
  1. package/API-SESSION.md +35 -1
  2. package/API-WEB-COMPONENT.md +2 -0
  3. package/CHANGELOG.md +109 -0
  4. package/CLIPBOARD.md +54 -0
  5. package/EDITING-SCENARIOS.md +209 -0
  6. package/GETTING-STARTED.md +54 -0
  7. package/IMPLEMENTATION.md +105 -0
  8. package/KEYBOARD.md +41 -0
  9. package/LATEX-GUIDE.md +52 -2
  10. package/LATEX-MODEL.md +12 -0
  11. package/LATEX-SCOPE.md +10 -1
  12. package/README.md +119 -23
  13. package/RELEASING.md +95 -10
  14. package/RENDERING-TESTS.md +79 -0
  15. package/ROADMAP.md +238 -6
  16. package/STYLING.md +28 -1
  17. package/TEXT-EDITORS.md +157 -0
  18. package/VALIDATION.md +433 -0
  19. package/dist/context-tools.d.ts +21 -0
  20. package/dist/context-tools.js +37 -0
  21. package/dist/dom/caret-geometry.js +1 -1
  22. package/dist/dom/context-keyboard.d.ts +2 -0
  23. package/dist/dom/context-keyboard.js +22 -0
  24. package/dist/dom/help.d.ts +5 -0
  25. package/dist/dom/help.js +104 -0
  26. package/dist/dom/menu-position.d.ts +3 -0
  27. package/dist/dom/menu-position.js +49 -4
  28. package/dist/dom/readable-layout.d.ts +3 -0
  29. package/dist/dom/readable-layout.js +52 -0
  30. package/dist/dom/toolbar-catalog.d.ts +1 -1
  31. package/dist/dom/toolbar.d.ts +2 -0
  32. package/dist/dom/toolbar.js +8 -1
  33. package/dist/dom.d.ts +13 -1
  34. package/dist/dom.js +323 -51
  35. package/dist/enter-policy.js +1 -1
  36. package/dist/latex.js +11 -0
  37. package/dist/lines.d.ts +2 -0
  38. package/dist/lines.js +15 -0
  39. package/dist/locales/en.js +33 -3
  40. package/dist/locales/en.json +33 -3
  41. package/dist/locales/ko.js +33 -3
  42. package/dist/locales/ko.json +33 -3
  43. package/dist/math-editor-toolbar.js +2 -1
  44. package/dist/math-editor.d.ts +4 -1
  45. package/dist/math-editor.js +506 -296
  46. package/dist/math-layout.d.ts +12 -0
  47. package/dist/math-layout.js +59 -0
  48. package/dist/math-spacing.d.ts +13 -0
  49. package/dist/math-spacing.js +87 -0
  50. package/dist/model.d.ts +6 -0
  51. package/dist/model.js +72 -5
  52. package/dist/range.d.ts +10 -2
  53. package/dist/range.js +31 -6
  54. package/dist/root-transform.d.ts +19 -0
  55. package/dist/root-transform.js +69 -0
  56. package/dist/selection-shortcuts.d.ts +13 -0
  57. package/dist/selection-shortcuts.js +35 -0
  58. package/dist/session.d.ts +1 -0
  59. package/dist/session.js +1 -1
  60. package/dist/suggestions.d.ts +9 -0
  61. package/dist/suggestions.js +53 -0
  62. package/dist/symbols.d.ts +1 -1
  63. package/dist/symbols.js +1 -0
  64. package/dist/vertical-navigation.d.ts +5 -0
  65. package/dist/vertical-navigation.js +33 -0
  66. package/dist/web-component.js +9 -1
  67. package/package.json +11 -3
  68. package/src/fonts/KaTeX_Math-Italic.woff2 +0 -0
  69. package/src/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  70. package/src/fonts/README.md +18 -0
  71. package/src/shapes/README.md +21 -0
  72. package/src/shapes/parenthesis-bottom.svg +1 -0
  73. package/src/shapes/parenthesis-top.svg +1 -0
  74. package/src/shapes/parenthesis.svg +1 -0
  75. package/src/shapes/radical.svg +1 -0
  76. package/src/style.css +882 -61
@@ -0,0 +1,104 @@
1
+ import { translate } from '../i18n.js';
2
+ const helpEvent = 'math-editor-help';
3
+ /** Route a toolbar request to its owning field, including fields inside an iframe. */
4
+ export function requestMathHelp(control) {
5
+ const EventClass = control.ownerDocument.defaultView.Event;
6
+ control.dispatchEvent(new EventClass(helpEvent, { bubbles: true }));
7
+ }
8
+ /** Shared help UI. It does not execute model commands or read the clipboard. */
9
+ export function attachMathHelp(root, locale) {
10
+ let dialog;
11
+ let previous = null;
12
+ let selection;
13
+ const close = (restore = true) => {
14
+ dialog?.close();
15
+ dialog?.remove();
16
+ dialog = undefined;
17
+ if (restore && previous?.isConnected) {
18
+ previous.focus({ preventScroll: true });
19
+ if (selection && previous.tagName === 'INPUT') {
20
+ previous.setSelectionRange(selection.start, selection.end, selection.direction);
21
+ previous.dispatchEvent(new root.ownerDocument.defaultView.Event('select', { bubbles: true }));
22
+ }
23
+ }
24
+ };
25
+ const open = () => {
26
+ if (dialog)
27
+ return;
28
+ const doc = root.ownerDocument;
29
+ const t = (key) => translate(locale(), key);
30
+ previous = doc.activeElement;
31
+ const input = previous?.tagName === 'INPUT' ? previous : undefined;
32
+ selection =
33
+ input && input.selectionStart !== null && input.selectionEnd !== null
34
+ ? {
35
+ start: input.selectionStart,
36
+ end: input.selectionEnd,
37
+ direction: input.selectionDirection ?? 'none',
38
+ }
39
+ : undefined;
40
+ dialog = doc.createElement('dialog');
41
+ dialog.className = 'me-help me-utility-panel';
42
+ dialog.setAttribute('aria-label', t('help.title'));
43
+ const heading = doc.createElement('h2');
44
+ heading.textContent = t('help.title');
45
+ const intro = doc.createElement('p');
46
+ intro.textContent = t('help.intro');
47
+ const list = doc.createElement('dl');
48
+ for (const [keys, message] of [
49
+ ['Tab / Shift+Tab', 'help.slots'],
50
+ ['Ctrl+←/→ · Mac: ⌥+←/→ (+Shift)', 'help.units'],
51
+ ['Shift+← / →', 'help.select'],
52
+ ['( [ { | / ^ _', 'help.wrap'],
53
+ ['Alt+↓', 'help.suggestions'],
54
+ ['↑ / ↓ · Enter · Esc', 'help.menu'],
55
+ ['Ctrl/Cmd+C · X · V', 'help.clipboard'],
56
+ ['Alt+Shift+V', 'help.latex'],
57
+ ['Ctrl/Cmd+Z · Ctrl/Cmd+Shift+Z', 'help.history'],
58
+ ['Enter / Shift+Enter', 'help.lines'],
59
+ ['F1', 'help.open'],
60
+ ]) {
61
+ const term = doc.createElement('dt');
62
+ term.textContent = keys;
63
+ const definition = doc.createElement('dd');
64
+ definition.textContent = t(message);
65
+ list.append(term, definition);
66
+ }
67
+ const note = doc.createElement('p');
68
+ note.textContent = t('help.platform');
69
+ const button = doc.createElement('button');
70
+ button.type = 'button';
71
+ button.textContent = t('common.close');
72
+ button.onclick = () => close();
73
+ dialog.append(heading, intro, list, note, button);
74
+ dialog.addEventListener('cancel', (event) => {
75
+ event.preventDefault();
76
+ close();
77
+ });
78
+ // Help text selection/copy must not invoke the formula's clipboard or keyboard handlers.
79
+ for (const name of ['copy', 'cut', 'paste', 'keydown', 'keyup'])
80
+ dialog.addEventListener(name, (event) => event.stopPropagation());
81
+ root.append(dialog);
82
+ dialog.showModal();
83
+ button.focus();
84
+ };
85
+ const keydown = (event) => {
86
+ if (event.key !== 'F1' ||
87
+ event.isComposing ||
88
+ event.ctrlKey ||
89
+ event.metaKey ||
90
+ event.altKey ||
91
+ event.shiftKey)
92
+ return;
93
+ event.preventDefault();
94
+ event.stopPropagation();
95
+ open();
96
+ };
97
+ root.addEventListener(helpEvent, open);
98
+ root.addEventListener('keydown', keydown, true);
99
+ return () => {
100
+ root.removeEventListener(helpEvent, open);
101
+ root.removeEventListener('keydown', keydown, true);
102
+ close(false);
103
+ };
104
+ }
@@ -4,6 +4,9 @@ interface MenuRect {
4
4
  right: number;
5
5
  bottom: number;
6
6
  }
7
+ /** Follow layout and visual viewport changes, including ancestor transforms that
8
+ * do not produce window resize or element ResizeObserver notifications. */
9
+ export declare function followMathMenuPosition(anchor: HTMLElement, position: () => void, ignoreScrollWithin?: HTMLElement): () => void;
7
10
  /** Choose a scrollable vertical gap without covering host controls in the same column. */
8
11
  export declare function mathMenuPlacement(bounds: MenuRect, anchor: MenuRect, width: number, desiredHeight: number, preferredHeight: number, avoid?: readonly MenuRect[]): {
9
12
  left: number;
@@ -1,3 +1,41 @@
1
+ /** Follow layout and visual viewport changes, including ancestor transforms that
2
+ * do not produce window resize or element ResizeObserver notifications. */
3
+ export function followMathMenuPosition(anchor, position, ignoreScrollWithin) {
4
+ const win = anchor.ownerDocument.defaultView;
5
+ let frame = 0;
6
+ const schedule = () => {
7
+ if (!frame)
8
+ frame = win.requestAnimationFrame(() => {
9
+ frame = 0;
10
+ position();
11
+ });
12
+ };
13
+ const scroll = (event) => {
14
+ if (event.target &&
15
+ 'nodeType' in event.target &&
16
+ ignoreScrollWithin?.contains(event.target))
17
+ return;
18
+ schedule();
19
+ };
20
+ const changes = new MutationObserver(schedule);
21
+ for (let element = anchor; element; element = element.parentElement)
22
+ changes.observe(element, { attributes: true, attributeFilter: ['style', 'class'] });
23
+ const resize = typeof ResizeObserver === 'undefined' ? undefined : new ResizeObserver(schedule);
24
+ resize?.observe(anchor);
25
+ win.addEventListener('resize', schedule);
26
+ win.addEventListener('scroll', scroll, true);
27
+ win.visualViewport?.addEventListener('resize', schedule);
28
+ win.visualViewport?.addEventListener('scroll', schedule);
29
+ return () => {
30
+ changes.disconnect();
31
+ resize?.disconnect();
32
+ win.cancelAnimationFrame(frame);
33
+ win.removeEventListener('resize', schedule);
34
+ win.removeEventListener('scroll', scroll, true);
35
+ win.visualViewport?.removeEventListener('resize', schedule);
36
+ win.visualViewport?.removeEventListener('scroll', schedule);
37
+ };
38
+ }
1
39
  /** Fixed menus escape ordinary scrolling text areas. Transforms and containment
2
40
  * establish a local containing block, so overflow at that block and above still clips. */
3
41
  function establishesFixedContainingBlock(style) {
@@ -68,15 +106,22 @@ export function mathMenuPlacement(bounds, anchor, width, desiredHeight, preferre
68
106
  /** Position a fixed portal in viewport coordinates, including transformed modal hosts. */
69
107
  export function positionMathMenu(menu, anchor, maxHeight, avoidElements = []) {
70
108
  const win = menu.ownerDocument.defaultView;
71
- const bounds = { left: 8, top: 8, right: win.innerWidth - 8, bottom: win.innerHeight - 8 };
109
+ const viewport = win.visualViewport;
110
+ const bounds = {
111
+ left: (viewport?.offsetLeft ?? 0) + 8,
112
+ top: (viewport?.offsetTop ?? 0) + 8,
113
+ right: (viewport?.offsetLeft ?? 0) + (viewport?.width ?? win.innerWidth) - 8,
114
+ bottom: (viewport?.offsetTop ?? 0) + (viewport?.height ?? win.innerHeight) - 8,
115
+ };
72
116
  let contained = false;
73
117
  for (let parent = menu.parentElement; parent; parent = parent.parentElement) {
74
118
  const style = win.getComputedStyle(parent);
75
119
  contained ||= establishesFixedContainingBlock(style);
76
- const dialog = parent.matches('dialog, [role="dialog"]');
77
120
  const paintClip = /paint|strict|content/.test(style.contain) || style.contentVisibility === 'auto';
78
- const clipX = dialog || paintClip || (contained && /auto|scroll|hidden|clip/.test(style.overflowX));
79
- const clipY = dialog || paintClip || (contained && /auto|scroll|hidden|clip/.test(style.overflowY));
121
+ // Dialog semantics do not create a CSS clipping boundary. A compact dialog
122
+ // can host a fixed menu taller than itself; constrain only actual paint clips.
123
+ const clipX = paintClip || (contained && /auto|scroll|hidden|clip/.test(style.overflowX));
124
+ const clipY = paintClip || (contained && /auto|scroll|hidden|clip/.test(style.overflowY));
80
125
  if (!clipX && !clipY)
81
126
  continue;
82
127
  const rect = parent.getBoundingClientRect();
@@ -0,0 +1,3 @@
1
+ /** Reserve line space for translated scripts. Their visual bounds are not part
2
+ * of flex layout, and a font floor can make them taller than the base row. */
3
+ export declare function followReadableMathLayout(surface: HTMLElement): () => void;
@@ -0,0 +1,52 @@
1
+ /** Reserve line space for translated scripts. Their visual bounds are not part
2
+ * of flex layout, and a font floor can make them taller than the base row. */
3
+ export function followReadableMathLayout(surface) {
4
+ const win = surface.ownerDocument.defaultView;
5
+ let frame = 0;
6
+ const measure = () => {
7
+ frame = 0;
8
+ const minimum = win.getComputedStyle(surface).getPropertyValue('--me-min-font-size').trim();
9
+ const enabled = !minimum || Number.parseFloat(minimum) !== 0;
10
+ for (const line of surface.querySelectorAll('.me-document-line')) {
11
+ const row = line.querySelector(':scope > .me-row');
12
+ if (!row)
13
+ continue;
14
+ const bounds = row.getBoundingClientRect();
15
+ let top = bounds.top, bottom = bounds.bottom;
16
+ if (enabled) {
17
+ for (const glyph of row.querySelectorAll('.me-measure, .me-delimiter, .me-radical, .me-operator-glyph')) {
18
+ const rect = glyph.getBoundingClientRect();
19
+ top = Math.min(top, rect.top);
20
+ bottom = Math.max(bottom, rect.bottom);
21
+ }
22
+ }
23
+ const before = `${Math.ceil(bounds.top - top)}px`;
24
+ const after = `${Math.ceil(bottom - bounds.bottom)}px`;
25
+ if (line.style.paddingTop !== before)
26
+ line.style.paddingTop = before;
27
+ if (line.style.paddingBottom !== after)
28
+ line.style.paddingBottom = after;
29
+ }
30
+ };
31
+ const schedule = () => {
32
+ if (!frame)
33
+ frame = win.requestAnimationFrame(measure);
34
+ };
35
+ const mutations = new MutationObserver(schedule);
36
+ mutations.observe(surface, {
37
+ childList: true,
38
+ subtree: true,
39
+ attributes: true,
40
+ attributeFilter: ['style', 'class'],
41
+ });
42
+ const resize = typeof ResizeObserver === 'undefined' ? undefined : new ResizeObserver(schedule);
43
+ resize?.observe(surface);
44
+ win.addEventListener('resize', schedule);
45
+ schedule();
46
+ return () => {
47
+ mutations.disconnect();
48
+ resize?.disconnect();
49
+ win.cancelAnimationFrame(frame);
50
+ win.removeEventListener('resize', schedule);
51
+ };
52
+ }
@@ -1,7 +1,7 @@
1
1
  import { type MathLocale } from '../i18n.js';
2
2
  import type { StructureKind } from '../model.js';
3
3
  /** Full discovery search retains the symbol catalog and suggestion engine's ranking. */
4
- export declare function findToolbarSymbols(query: string, locale: MathLocale): (readonly ["↑", "symbol.upArrow", "uparrow arrow 화살표", "- ->", "\\uparrow"] | readonly ["↕", "symbol.upDownArrow", "updownarrow arrow 화살표", "- ->", "\\updownarrow"] | readonly ["⇐", "symbol.doubleLeftArrow", "Leftarrow arrow 화살표", "- ->", "\\Leftarrow"] | readonly ["⇑", "symbol.doubleUpArrow", "Uparrow arrow 화살표", "- ->", "\\Uparrow"] | readonly ["⇓", "symbol.doubleDownArrow", "Downarrow arrow 화살표", "- ->", "\\Downarrow"] | readonly ["⇕", "symbol.doubleUpDownArrow", "Updownarrow arrow 화살표", "- ->", "\\Updownarrow"] | readonly ["↗", "symbol.northEastArrow", "nearrow arrow 화살표", "- ->", "\\nearrow"] | readonly ["↘", "symbol.southEastArrow", "searrow arrow 화살표", "- ->", "\\searrow"] | readonly ["↙", "symbol.southWestArrow", "swarrow arrow 화살표", "- ->", "\\swarrow"] | readonly ["↖", "symbol.northWestArrow", "nwarrow arrow 화살표", "- ->", "\\nwarrow"] | readonly ["⟶", "symbol.longRightArrow", "longrightarrow arrow 화살표", "- ->", "\\longrightarrow"] | readonly ["⟵", "symbol.longLeftArrow", "longleftarrow arrow 화살표", "- ->", "\\longleftarrow"] | readonly ["⟷", "symbol.longLeftRightArrow", "longleftrightarrow arrow 화살표", "-", "\\longleftrightarrow"] | readonly ["⟹", "symbol.longDoubleRightArrow", "Longrightarrow arrow 화살표", "- ->", "\\Longrightarrow"] | readonly ["⟸", "symbol.longDoubleLeftArrow", "Longleftarrow arrow 화살표", "- ->", "\\Longleftarrow"] | readonly ["⟺", "symbol.longDoubleLeftRightArrow", "Longleftrightarrow arrow 화살표", "", "\\Longleftrightarrow"] | readonly ["↪", "symbol.hookRightArrow", "hookrightarrow arrow 화살표", "- ->", "\\hookrightarrow"] | readonly ["↩", "symbol.hookLeftArrow", "hookleftarrow arrow 화살표", "- ->", "\\hookleftarrow"] | readonly ["⇀", "symbol.rightHarpoonUp", "rightharpoonup arrow 화살표", "- ->", "\\rightharpoonup"] | readonly ["↼", "symbol.leftHarpoonUp", "leftharpoonup arrow 화살표", "- ->", "\\leftharpoonup"] | readonly ["⇁", "symbol.rightHarpoonDown", "rightharpoondown arrow 화살표", "- ->", "\\rightharpoondown"] | readonly ["↽", "symbol.leftHarpoonDown", "leftharpoondown arrow 화살표", "- ->", "\\leftharpoondown"] | readonly ["⇌", "symbol.rightLeftHarpoons", "rightleftharpoons arrow 화살표", "- ->", "\\rightleftharpoons"] | readonly ["⇋", "symbol.leftRightHarpoons", "leftrightharpoons arrow 화살표", "- ->", "\\leftrightharpoons"] | readonly ["↓", "symbol.downArrow", "downarrow down 아래화살표 아래쪽화살표", "- ->", "\\downarrow"] | readonly ["⩾", "symbol.greaterOrEqualSlanted", "geqslant", "", "\\geqslant"] | readonly ["⩽", "symbol.lessOrEqualSlanted", "leqslant", "", "\\leqslant"] | readonly ["∋", "symbol.contains", "ni contains", "", "\\ni"] | readonly ["⊃", "symbol.superset", "supset superset", "", "\\supset"] | readonly ["⊇", "symbol.supersetOrEqual", "supseteq", "", "\\supseteq"] | readonly ["⊈", "symbol.notSubsetOrEqual", "nsubseteq", "", "\\nsubseteq"] | readonly ["∖", "symbol.setDifference", "setminus difference", "", "\\setminus"] | readonly ["ℕ", "symbol.naturals", "naturals natural", "", "\\mathbb{N}"] | readonly ["ℤ", "symbol.integers", "integers integer", "", "\\mathbb{Z}"] | readonly ["ℚ", "symbol.rationals", "rationals rational", "", "\\mathbb{Q}"] | readonly ["ℝ", "symbol.reals", "reals real", "", "\\mathbb{R}"] | readonly ["ℂ", "symbol.complexes", "complexes complex", "", "\\mathbb{C}"] | readonly ["⊥", "symbol.perpendicular", "perp perpendicular", "", "\\perp"] | readonly ["∥", "symbol.parallel", "parallel", "", "\\parallel"] | readonly ["≅", "symbol.congruent", "cong congruent", "", "\\cong"] | readonly ["∼", "symbol.similar", "sim similar", "", "\\sim"] | readonly ["↦", "symbol.mapsTo", "mapsto maps arrow 화살표", "|-> - ->", "\\mapsto"] | readonly ["…", "symbol.ellipsis", "ldots ellipsis", "", "\\ldots"] | readonly ["⋯", "symbol.centeredDots", "cdots", "", "\\cdots"] | readonly ["⋮", "symbol.verticalDots", "vdots", "", "\\vdots"] | readonly ["⋱", "symbol.diagonalDots", "ddots", "", "\\ddots"] | readonly ["°", "symbol.degree", "degree degrees 각도", "", "{}^{\\circ}"] | readonly ["∠", "symbol.angle", "angle", "", "\\angle"] | readonly ["+", "symbol.plus", "plus sum", "+", "+"] | readonly ["−", "symbol.minus", "minus", "-", "-"] | readonly ["±", "symbol.plusMinus", "plusminus sum", "+- +", "\\pm"] | readonly ["∓", "symbol.minusPlus", "minusplus", "-+ -", "\\mp"] | readonly ["×", "symbol.times", "times multiply", "*", "\\times"] | readonly ["·", "symbol.dot", "dot multiply", "*", "\\cdot"] | readonly ["÷", "symbol.divide", "divide division", "/", "\\div"] | readonly ["=", "symbol.equal", "equal", "=", "="] | readonly ["≠", "symbol.notEqual", "neq unequal", "!= /= =", "\\ne"] | readonly ["≈", "symbol.approximatelyEqual", "approx", "~ ~~ =", "\\approx"] | readonly ["≡", "symbol.equivalent", "equiv", "=== =", "\\equiv"] | readonly ["<", "symbol.lessThan", "less", "<", "<"] | readonly [">", "symbol.greaterThan", "greater", ">", ">"] | readonly ["≤", "symbol.lessOrEqual", "leq less", "<= <- <", "\\le"] | readonly ["≥", "symbol.greaterOrEqual", "geq greater", ">= >- >", "\\ge"] | readonly ["∝", "symbol.proportional", "proportional", "~", "\\propto"] | readonly ["→", "symbol.rightArrow", "rightarrow arrow", "-> -", "\\to"] | readonly ["←", "symbol.leftArrow", "leftarrow arrow", "<- < - ->", "\\leftarrow"] | readonly ["↔", "symbol.leftRightArrow", "leftrightarrow arrow", "<-> - ->", "\\leftrightarrow"] | readonly ["⇒", "symbol.implies", "implies arrow 화살표", "=> = - ->", "\\Rightarrow"] | readonly ["⇔", "symbol.iff", "iff arrow 화살표", "<=> - ->", "\\Leftrightarrow"] | readonly ["∈", "symbol.elementOf", "in element", "E", "\\in"] | readonly ["∉", "symbol.notElementOf", "notin element", "E/", "\\notin"] | readonly ["∀", "symbol.forAll", "forall", "A", "\\forall"] | readonly ["∃", "symbol.exists", "exists", "E", "\\exists"] | readonly ["∪", "symbol.union", "union", "U", "\\cup"] | readonly ["∩", "symbol.intersection", "intersection", "", "\\cap"] | readonly ["⊂", "symbol.subset", "subset", "<", "\\subset"] | readonly ["⊆", "symbol.subsetOrEqual", "subseteq", "<=", "\\subseteq"] | readonly ["∅", "symbol.emptySet", "emptyset", "", "\\emptyset"] | readonly ["∞", "symbol.infinity", "infinity infty", "", "\\infty"] | readonly ["∂", "symbol.partialDerivative", "partial derivative", "", "\\partial"] | readonly ["∇", "symbol.nabla", "nabla gradient", "", "\\nabla"] | readonly ["∧", "symbol.logicalAnd", "and wedge", "^", "\\wedge"] | readonly ["∨", "symbol.logicalOr", "or vee", "", "\\vee"] | readonly ["¬", "symbol.logicalNot", "not neg", "!", "\\neg"] | readonly ["α", "symbol.alpha", "alpha", "", "\\alpha"] | readonly ["β", "symbol.beta", "beta", "", "\\beta"] | readonly ["γ", "symbol.gamma", "gamma", "", "\\gamma"] | readonly ["δ", "symbol.delta", "delta", "", "\\delta"] | readonly ["ε", "symbol.epsilon", "epsilon", "", "\\epsilon"] | readonly ["ζ", "symbol.zeta", "zeta", "", "\\zeta"] | readonly ["η", "symbol.eta", "eta", "", "\\eta"] | readonly ["θ", "symbol.theta", "theta", "", "\\theta"] | readonly ["ι", "symbol.iota", "iota", "", "\\iota"] | readonly ["κ", "symbol.kappa", "kappa", "", "\\kappa"] | readonly ["λ", "symbol.lambda", "lambda", "", "\\lambda"] | readonly ["μ", "symbol.mu", "mu", "", "\\mu"] | readonly ["ν", "symbol.nu", "nu", "", "\\nu"] | readonly ["ξ", "symbol.xi", "xi", "", "\\xi"] | readonly ["π", "symbol.pi", "pi", "", "\\pi"] | readonly ["ρ", "symbol.rho", "rho", "", "\\rho"] | readonly ["σ", "symbol.sigma", "sigma", "", "\\sigma"] | readonly ["τ", "symbol.tau", "tau", "", "\\tau"] | readonly ["υ", "symbol.upsilon", "upsilon", "", "\\upsilon"] | readonly ["φ", "symbol.phi", "phi", "", "\\phi"] | readonly ["χ", "symbol.chi", "chi", "", "\\chi"] | readonly ["ψ", "symbol.psi", "psi", "", "\\psi"] | readonly ["ω", "symbol.omega", "omega", "", "\\omega"] | readonly ["Γ", "symbol.upperGamma", "Gamma", "", "\\Gamma"] | readonly ["Δ", "symbol.upperDelta", "Delta", "", "\\Delta"] | readonly ["Θ", "symbol.upperTheta", "Theta", "", "\\Theta"] | readonly ["Λ", "symbol.upperLambda", "Lambda", "", "\\Lambda"] | readonly ["Ξ", "symbol.upperXi", "Xi", "", "\\Xi"] | readonly ["Π", "symbol.upperPi", "Pi", "", "\\Pi"] | readonly ["Σ", "symbol.upperSigma", "Sigma", "", "\\Sigma"] | readonly ["Φ", "symbol.upperPhi", "Phi", "", "\\Phi"] | readonly ["Ψ", "symbol.upperPsi", "Psi", "", "\\Psi"] | readonly ["Ω", "symbol.upperOmega", "Omega", "", "\\Omega"])[];
4
+ export declare function findToolbarSymbols(query: string, locale: MathLocale): (readonly ["↑", "symbol.upArrow", "uparrow arrow 화살표", "- ->", "\\uparrow"] | readonly ["↕", "symbol.upDownArrow", "updownarrow arrow 화살표", "- ->", "\\updownarrow"] | readonly ["⇐", "symbol.doubleLeftArrow", "Leftarrow arrow 화살표", "- ->", "\\Leftarrow"] | readonly ["⇑", "symbol.doubleUpArrow", "Uparrow arrow 화살표", "- ->", "\\Uparrow"] | readonly ["⇓", "symbol.doubleDownArrow", "Downarrow arrow 화살표", "- ->", "\\Downarrow"] | readonly ["⇕", "symbol.doubleUpDownArrow", "Updownarrow arrow 화살표", "- ->", "\\Updownarrow"] | readonly ["↗", "symbol.northEastArrow", "nearrow arrow 화살표", "- ->", "\\nearrow"] | readonly ["↘", "symbol.southEastArrow", "searrow arrow 화살표", "- ->", "\\searrow"] | readonly ["↙", "symbol.southWestArrow", "swarrow arrow 화살표", "- ->", "\\swarrow"] | readonly ["↖", "symbol.northWestArrow", "nwarrow arrow 화살표", "- ->", "\\nwarrow"] | readonly ["⟶", "symbol.longRightArrow", "longrightarrow arrow 화살표", "- ->", "\\longrightarrow"] | readonly ["⟵", "symbol.longLeftArrow", "longleftarrow arrow 화살표", "- ->", "\\longleftarrow"] | readonly ["⟷", "symbol.longLeftRightArrow", "longleftrightarrow arrow 화살표", "-", "\\longleftrightarrow"] | readonly ["⟹", "symbol.longDoubleRightArrow", "Longrightarrow arrow 화살표", "- ->", "\\Longrightarrow"] | readonly ["⟸", "symbol.longDoubleLeftArrow", "Longleftarrow arrow 화살표", "- ->", "\\Longleftarrow"] | readonly ["⟺", "symbol.longDoubleLeftRightArrow", "Longleftrightarrow arrow 화살표", "", "\\Longleftrightarrow"] | readonly ["↪", "symbol.hookRightArrow", "hookrightarrow arrow 화살표", "- ->", "\\hookrightarrow"] | readonly ["↩", "symbol.hookLeftArrow", "hookleftarrow arrow 화살표", "- ->", "\\hookleftarrow"] | readonly ["⇀", "symbol.rightHarpoonUp", "rightharpoonup arrow 화살표", "- ->", "\\rightharpoonup"] | readonly ["↼", "symbol.leftHarpoonUp", "leftharpoonup arrow 화살표", "- ->", "\\leftharpoonup"] | readonly ["⇁", "symbol.rightHarpoonDown", "rightharpoondown arrow 화살표", "- ->", "\\rightharpoondown"] | readonly ["↽", "symbol.leftHarpoonDown", "leftharpoondown arrow 화살표", "- ->", "\\leftharpoondown"] | readonly ["⇌", "symbol.rightLeftHarpoons", "rightleftharpoons arrow 화살표", "- ->", "\\rightleftharpoons"] | readonly ["⇋", "symbol.leftRightHarpoons", "leftrightharpoons arrow 화살표", "- ->", "\\leftrightharpoons"] | readonly ["↓", "symbol.downArrow", "downarrow down 아래화살표 아래쪽화살표", "- ->", "\\downarrow"] | readonly ["⩾", "symbol.greaterOrEqualSlanted", "geqslant", "", "\\geqslant"] | readonly ["⩽", "symbol.lessOrEqualSlanted", "leqslant", "", "\\leqslant"] | readonly ["∋", "symbol.contains", "ni contains", "", "\\ni"] | readonly ["⊃", "symbol.superset", "supset superset", "", "\\supset"] | readonly ["⊇", "symbol.supersetOrEqual", "supseteq", "", "\\supseteq"] | readonly ["⊈", "symbol.notSubsetOrEqual", "nsubseteq", "", "\\nsubseteq"] | readonly ["∖", "symbol.setDifference", "setminus difference", "", "\\setminus"] | readonly ["ℕ", "symbol.naturals", "naturals natural", "", "\\mathbb{N}"] | readonly ["ℤ", "symbol.integers", "integers integer", "", "\\mathbb{Z}"] | readonly ["ℚ", "symbol.rationals", "rationals rational", "", "\\mathbb{Q}"] | readonly ["ℝ", "symbol.reals", "reals real", "", "\\mathbb{R}"] | readonly ["ℂ", "symbol.complexes", "complexes complex", "", "\\mathbb{C}"] | readonly ["⊥", "symbol.perpendicular", "perp perpendicular", "", "\\perp"] | readonly ["∥", "symbol.parallel", "parallel", "", "\\parallel"] | readonly ["≅", "symbol.congruent", "cong congruent", "", "\\cong"] | readonly ["∼", "symbol.similar", "sim similar", "", "\\sim"] | readonly ["↦", "symbol.mapsTo", "mapsto maps arrow 화살표", "|-> - ->", "\\mapsto"] | readonly ["…", "symbol.ellipsis", "ldots ellipsis", "", "\\ldots"] | readonly ["⋯", "symbol.centeredDots", "cdots", "", "\\cdots"] | readonly ["⋮", "symbol.verticalDots", "vdots", "", "\\vdots"] | readonly ["⋱", "symbol.diagonalDots", "ddots", "", "\\ddots"] | readonly ["′", "symbol.prime", "prime 프라임 미분", "", "\\prime"] | readonly ["°", "symbol.degree", "degree degrees 각도", "", "{}^{\\circ}"] | readonly ["∠", "symbol.angle", "angle", "", "\\angle"] | readonly ["+", "symbol.plus", "plus sum", "+", "+"] | readonly ["−", "symbol.minus", "minus", "-", "-"] | readonly ["±", "symbol.plusMinus", "plusminus sum", "+- +", "\\pm"] | readonly ["∓", "symbol.minusPlus", "minusplus", "-+ -", "\\mp"] | readonly ["×", "symbol.times", "times multiply", "*", "\\times"] | readonly ["·", "symbol.dot", "dot multiply", "*", "\\cdot"] | readonly ["÷", "symbol.divide", "divide division", "/", "\\div"] | readonly ["=", "symbol.equal", "equal", "=", "="] | readonly ["≠", "symbol.notEqual", "neq unequal", "!= /= =", "\\ne"] | readonly ["≈", "symbol.approximatelyEqual", "approx", "~ ~~ =", "\\approx"] | readonly ["≡", "symbol.equivalent", "equiv", "=== =", "\\equiv"] | readonly ["<", "symbol.lessThan", "less", "<", "<"] | readonly [">", "symbol.greaterThan", "greater", ">", ">"] | readonly ["≤", "symbol.lessOrEqual", "leq less", "<= <- <", "\\le"] | readonly ["≥", "symbol.greaterOrEqual", "geq greater", ">= >- >", "\\ge"] | readonly ["∝", "symbol.proportional", "proportional", "~", "\\propto"] | readonly ["→", "symbol.rightArrow", "rightarrow arrow", "-> -", "\\to"] | readonly ["←", "symbol.leftArrow", "leftarrow arrow", "<- < - ->", "\\leftarrow"] | readonly ["↔", "symbol.leftRightArrow", "leftrightarrow arrow", "<-> - ->", "\\leftrightarrow"] | readonly ["⇒", "symbol.implies", "implies arrow 화살표", "=> = - ->", "\\Rightarrow"] | readonly ["⇔", "symbol.iff", "iff arrow 화살표", "<=> - ->", "\\Leftrightarrow"] | readonly ["∈", "symbol.elementOf", "in element", "E", "\\in"] | readonly ["∉", "symbol.notElementOf", "notin element", "E/", "\\notin"] | readonly ["∀", "symbol.forAll", "forall", "A", "\\forall"] | readonly ["∃", "symbol.exists", "exists", "E", "\\exists"] | readonly ["∪", "symbol.union", "union", "U", "\\cup"] | readonly ["∩", "symbol.intersection", "intersection", "", "\\cap"] | readonly ["⊂", "symbol.subset", "subset", "<", "\\subset"] | readonly ["⊆", "symbol.subsetOrEqual", "subseteq", "<=", "\\subseteq"] | readonly ["∅", "symbol.emptySet", "emptyset", "", "\\emptyset"] | readonly ["∞", "symbol.infinity", "infinity infty", "", "\\infty"] | readonly ["∂", "symbol.partialDerivative", "partial derivative", "", "\\partial"] | readonly ["∇", "symbol.nabla", "nabla gradient", "", "\\nabla"] | readonly ["∧", "symbol.logicalAnd", "and wedge", "^", "\\wedge"] | readonly ["∨", "symbol.logicalOr", "or vee", "", "\\vee"] | readonly ["¬", "symbol.logicalNot", "not neg", "!", "\\neg"] | readonly ["α", "symbol.alpha", "alpha", "", "\\alpha"] | readonly ["β", "symbol.beta", "beta", "", "\\beta"] | readonly ["γ", "symbol.gamma", "gamma", "", "\\gamma"] | readonly ["δ", "symbol.delta", "delta", "", "\\delta"] | readonly ["ε", "symbol.epsilon", "epsilon", "", "\\epsilon"] | readonly ["ζ", "symbol.zeta", "zeta", "", "\\zeta"] | readonly ["η", "symbol.eta", "eta", "", "\\eta"] | readonly ["θ", "symbol.theta", "theta", "", "\\theta"] | readonly ["ι", "symbol.iota", "iota", "", "\\iota"] | readonly ["κ", "symbol.kappa", "kappa", "", "\\kappa"] | readonly ["λ", "symbol.lambda", "lambda", "", "\\lambda"] | readonly ["μ", "symbol.mu", "mu", "", "\\mu"] | readonly ["ν", "symbol.nu", "nu", "", "\\nu"] | readonly ["ξ", "symbol.xi", "xi", "", "\\xi"] | readonly ["π", "symbol.pi", "pi", "", "\\pi"] | readonly ["ρ", "symbol.rho", "rho", "", "\\rho"] | readonly ["σ", "symbol.sigma", "sigma", "", "\\sigma"] | readonly ["τ", "symbol.tau", "tau", "", "\\tau"] | readonly ["υ", "symbol.upsilon", "upsilon", "", "\\upsilon"] | readonly ["φ", "symbol.phi", "phi", "", "\\phi"] | readonly ["χ", "symbol.chi", "chi", "", "\\chi"] | readonly ["ψ", "symbol.psi", "psi", "", "\\psi"] | readonly ["ω", "symbol.omega", "omega", "", "\\omega"] | readonly ["Γ", "symbol.upperGamma", "Gamma", "", "\\Gamma"] | readonly ["Δ", "symbol.upperDelta", "Delta", "", "\\Delta"] | readonly ["Θ", "symbol.upperTheta", "Theta", "", "\\Theta"] | readonly ["Λ", "symbol.upperLambda", "Lambda", "", "\\Lambda"] | readonly ["Ξ", "symbol.upperXi", "Xi", "", "\\Xi"] | readonly ["Π", "symbol.upperPi", "Pi", "", "\\Pi"] | readonly ["Σ", "symbol.upperSigma", "Sigma", "", "\\Sigma"] | readonly ["Φ", "symbol.upperPhi", "Phi", "", "\\Phi"] | readonly ["Ψ", "symbol.upperPsi", "Psi", "", "\\Psi"] | readonly ["Ω", "symbol.upperOmega", "Omega", "", "\\Omega"])[];
5
5
  /** Templates must not offer a back door around a host's allowed structure kinds. */
6
6
  export declare function toolbarTemplates(kinds?: readonly StructureKind[]): import("../templates.js").MathTemplate[];
7
7
  export declare function parseToolbarMatrixPreset(value: string): {
@@ -9,6 +9,8 @@ export interface MathToolbarOptions {
9
9
  locale?: MathLocale;
10
10
  onExecute?: () => void;
11
11
  onPasteLatex?: () => void;
12
+ /** Connect help to the field when the toolbar is mounted separately. */
13
+ onHelp?: () => void;
12
14
  /** Close another utility owned by the editing surface before opening a toolbar panel. */
13
15
  onOpenPanel?: () => void;
14
16
  }
@@ -1,3 +1,4 @@
1
+ import { requestMathHelp } from './help.js';
1
2
  import { drawPresentationControls } from './presentation-controls.js';
2
3
  import { mountQuickPanel } from './quick-panel.js';
3
4
  import { mountLatexPanel } from './latex-panel.js';
@@ -87,6 +88,12 @@ export function mountMathToolbar(host, session, options = {}) {
87
88
  control.dataset.mathTool = item.kind;
88
89
  toolbar.append(control);
89
90
  }
91
+ if (options.onHelp || host.closest('.me-editor')) {
92
+ const help = button(t('help.title'), () => options.onHelp ? options.onHelp() : requestMathHelp(help));
93
+ help.dataset.mathTool = 'help';
94
+ help.setAttribute('aria-keyshortcuts', 'F1');
95
+ toolbar.append(help);
96
+ }
90
97
  if (expanded) {
91
98
  const quick = button(t('quick.title'), () => {
92
99
  options.onOpenPanel?.();
@@ -283,7 +290,7 @@ export function mountMathToolbar(host, session, options = {}) {
283
290
  control.dataset.mathTool = `${axis}-${action}`;
284
291
  if (action === 'delete') {
285
292
  control.title = t(axis === 'row' ? 'grid.deleteRowShortcut' : 'grid.deleteColumnShortcut');
286
- control.setAttribute('aria-keyshortcuts', axis === 'row' ? 'Alt+Shift+ArrowUp' : 'Alt+Shift+ArrowLeft');
293
+ control.setAttribute('aria-keyshortcuts', axis === 'row' ? 'Alt+Shift+ArrowUp' : 'Alt+Shift+Backspace');
287
294
  }
288
295
  gridTools.append(control);
289
296
  }
package/dist/dom.d.ts CHANGED
@@ -3,7 +3,8 @@ export { mountMathLatex, mountMathPreview, type MathOutput, type MathPreviewOpti
3
3
  export { mountMathToolbar, type MathToolbarOptions } from './dom/toolbar.js';
4
4
  import { type MathSession, type MathEditorMode } from './session.js';
5
5
  import { type MathLocale } from './i18n.js';
6
- import { type MathDocument, type StructureKind } from './model.js';
6
+ import { type MathDocument, type MathState, type StructureKind } from './model.js';
7
+ import { type MathSuggestion } from './suggestions.js';
7
8
  export interface DOMMathEditorOptions {
8
9
  /** Mount-only UI preferences; ignored when an external session owns the store. */
9
10
  preferences?: MathPreferences;
@@ -17,11 +18,21 @@ export interface DOMMathEditorOptions {
17
18
  mode?: MathEditorMode;
18
19
  toolbar?: boolean | readonly StructureKind[];
19
20
  toolbarMaxItems?: number;
21
+ /** Show contextual structure actions below the editing surface. Defaults to true. */
22
+ contextTools?: boolean;
23
+ /** Disable built-in suggestion and selection popups when the host renders its own controls. */
24
+ suggestionMenu?: boolean;
25
+ /** Filter or reorder generated suggestions without replacing editor selection/acceptance behavior. */
26
+ filterSuggestions?: (candidates: readonly MathSuggestion[], query: string, state: MathState) => readonly MathSuggestion[];
27
+ /** Limit selection wrapping actions for a host. Omit to show every supported kind. */
28
+ selectionKinds?: readonly StructureKind[];
20
29
  showLineNumbers?: boolean;
21
30
  onChange?: (document: MathDocument, latex: string) => void;
22
31
  /** Final DOM/caret notification, including focus and selection-only updates. */
23
32
  onRender?: () => void;
24
33
  onExit?: (direction: -1 | 1) => void;
34
+ /** Host structure commands. Return true when handled; preserve suggestions where appropriate. */
35
+ onEditKeyDown?: (event: KeyboardEvent, snapshot: ReturnType<MathSession['getSnapshot']>, acceptsSuggestion: boolean) => boolean;
25
36
  /** Default: block=newline, inline=commit. Suggestions always take priority. */
26
37
  enterBehavior?: 'newline' | 'commit';
27
38
  /** The host creates the next block, saves a popup, or restores its caret. No model mutation. */
@@ -32,6 +43,7 @@ export interface DOMMathEditorOptions {
32
43
  export interface DOMMathEditor {
33
44
  session: MathSession;
34
45
  focus(): void;
46
+ showHelp(): void;
35
47
  update(options: Partial<Omit<DOMMathEditorOptions, 'session' | 'defaultValue' | 'menuHost'>>): void;
36
48
  destroy(): void;
37
49
  }