@barocss/math-editor 0.2.1 → 0.4.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 (180) hide show
  1. package/ADAPTERS.md +25 -14
  2. package/API-JAVASCRIPT.md +1 -1
  3. package/API-REACT.md +5 -1
  4. package/API-SESSION.md +168 -6
  5. package/API-SOLID.md +1 -1
  6. package/API-SVELTE.md +1 -1
  7. package/API-VUE.md +1 -1
  8. package/API-WEB-COMPONENT.md +1 -1
  9. package/CHANGELOG.md +45 -0
  10. package/EMBEDDING.md +4 -0
  11. package/IMPLEMENTATION.md +27 -2
  12. package/JSON-MODEL.md +1 -0
  13. package/LATEX-GUIDE.md +8 -1
  14. package/LATEX-SCOPE.md +2 -2
  15. package/LICENSE +1 -3
  16. package/README.md +145 -37
  17. package/ROADMAP.md +93 -11
  18. package/STYLING.md +157 -0
  19. package/SUPPORT.md +24 -12
  20. package/VALIDATION.md +113 -4
  21. package/dist/core.d.ts +5 -1
  22. package/dist/core.js +5 -1
  23. package/dist/document-codec.d.ts +0 -1
  24. package/dist/document-codec.js +0 -1
  25. package/dist/dom/caret-geometry.d.ts +3 -0
  26. package/dist/dom/caret-geometry.js +41 -0
  27. package/dist/dom/elements.d.ts +1 -2
  28. package/dist/dom/elements.js +2 -3
  29. package/dist/dom/latex-panel.d.ts +10 -0
  30. package/dist/dom/latex-panel.js +59 -0
  31. package/dist/dom/matrix-selection.d.ts +11 -0
  32. package/dist/dom/matrix-selection.js +30 -0
  33. package/dist/dom/menu-position.d.ts +14 -2
  34. package/dist/dom/menu-position.js +84 -15
  35. package/dist/dom/presentation-controls.d.ts +4 -0
  36. package/dist/dom/presentation-controls.js +56 -0
  37. package/dist/dom/quick-panel.d.ts +12 -0
  38. package/dist/dom/quick-panel.js +102 -0
  39. package/dist/dom/selection.d.ts +29 -0
  40. package/dist/dom/selection.js +53 -0
  41. package/dist/dom/symbol-panel.d.ts +9 -0
  42. package/dist/dom/symbol-panel.js +146 -0
  43. package/dist/dom/theme.d.ts +6 -0
  44. package/dist/dom/theme.js +68 -0
  45. package/dist/dom/toolbar-catalog.d.ts +10 -0
  46. package/dist/dom/toolbar-catalog.js +35 -0
  47. package/dist/dom/toolbar.d.ts +13 -2
  48. package/dist/dom/toolbar.js +295 -28
  49. package/dist/dom.d.ts +7 -1
  50. package/dist/dom.js +510 -128
  51. package/dist/editor-labels.d.ts +0 -1
  52. package/dist/editor-labels.js +0 -1
  53. package/dist/enter-policy.d.ts +0 -1
  54. package/dist/enter-policy.js +0 -1
  55. package/dist/fences.d.ts +0 -1
  56. package/dist/fences.js +0 -1
  57. package/dist/i18n.d.ts +0 -1
  58. package/dist/i18n.js +0 -1
  59. package/dist/index.d.ts +4 -1
  60. package/dist/index.js +4 -1
  61. package/dist/latex-insertion.d.ts +12 -0
  62. package/dist/latex-insertion.js +26 -0
  63. package/dist/latex-paste-panel.d.ts +3 -0
  64. package/dist/latex-paste-panel.js +18 -0
  65. package/dist/latex.d.ts +0 -1
  66. package/dist/latex.js +0 -1
  67. package/dist/lines.d.ts +0 -1
  68. package/dist/lines.js +0 -1
  69. package/dist/locales/en.js +27 -1
  70. package/dist/locales/en.json +27 -1
  71. package/dist/locales/ko.js +27 -1
  72. package/dist/locales/ko.json +27 -1
  73. package/dist/math-editor-toolbar.d.ts +4 -2
  74. package/dist/math-editor-toolbar.js +3 -4
  75. package/dist/math-editor.d.ts +5 -2
  76. package/dist/math-editor.js +285 -46
  77. package/dist/matrix-range.d.ts +40 -0
  78. package/dist/matrix-range.js +242 -0
  79. package/dist/matrix.d.ts +0 -1
  80. package/dist/matrix.js +0 -1
  81. package/dist/model.d.ts +0 -1
  82. package/dist/model.js +0 -1
  83. package/dist/outputs.d.ts +0 -1
  84. package/dist/outputs.js +0 -1
  85. package/dist/preferences.d.ts +24 -0
  86. package/dist/preferences.js +66 -0
  87. package/dist/presentation-controls.d.ts +10 -0
  88. package/dist/presentation-controls.js +10 -0
  89. package/dist/presentation.d.ts +13 -0
  90. package/dist/presentation.js +86 -0
  91. package/dist/quick-panel.d.ts +2 -0
  92. package/dist/quick-panel.js +17 -0
  93. package/dist/range.d.ts +2 -1
  94. package/dist/range.js +11 -1
  95. package/dist/react.d.ts +0 -1
  96. package/dist/react.js +0 -1
  97. package/dist/session.d.ts +31 -2
  98. package/dist/session.js +108 -7
  99. package/dist/solid.d.ts +0 -1
  100. package/dist/solid.js +0 -1
  101. package/dist/suggestions.d.ts +4 -1
  102. package/dist/suggestions.js +100 -12
  103. package/dist/svelte.d.ts +0 -1
  104. package/dist/svelte.js +0 -1
  105. package/dist/symbol-browser.d.ts +0 -1
  106. package/dist/symbol-browser.js +0 -1
  107. package/dist/symbols.d.ts +0 -1
  108. package/dist/symbols.js +0 -1
  109. package/dist/templates.d.ts +0 -1
  110. package/dist/templates.js +0 -1
  111. package/dist/tokens.d.ts +0 -1
  112. package/dist/tokens.js +0 -1
  113. package/dist/vertical-navigation.d.ts +15 -0
  114. package/dist/vertical-navigation.js +145 -0
  115. package/dist/vue.d.ts +0 -1
  116. package/dist/vue.js +0 -1
  117. package/dist/web-component.d.ts +0 -1
  118. package/dist/web-component.js +0 -1
  119. package/package.json +1 -1
  120. package/src/style.css +254 -129
  121. package/dist/core.d.ts.map +0 -1
  122. package/dist/core.js.map +0 -1
  123. package/dist/document-codec.d.ts.map +0 -1
  124. package/dist/document-codec.js.map +0 -1
  125. package/dist/dom/elements.d.ts.map +0 -1
  126. package/dist/dom/elements.js.map +0 -1
  127. package/dist/dom/menu-position.d.ts.map +0 -1
  128. package/dist/dom/menu-position.js.map +0 -1
  129. package/dist/dom/toolbar.d.ts.map +0 -1
  130. package/dist/dom/toolbar.js.map +0 -1
  131. package/dist/dom.d.ts.map +0 -1
  132. package/dist/dom.js.map +0 -1
  133. package/dist/editor-labels.d.ts.map +0 -1
  134. package/dist/editor-labels.js.map +0 -1
  135. package/dist/enter-policy.d.ts.map +0 -1
  136. package/dist/enter-policy.js.map +0 -1
  137. package/dist/fences.d.ts.map +0 -1
  138. package/dist/fences.js.map +0 -1
  139. package/dist/i18n.d.ts.map +0 -1
  140. package/dist/i18n.js.map +0 -1
  141. package/dist/index.d.ts.map +0 -1
  142. package/dist/index.js.map +0 -1
  143. package/dist/latex.d.ts.map +0 -1
  144. package/dist/latex.js.map +0 -1
  145. package/dist/lines.d.ts.map +0 -1
  146. package/dist/lines.js.map +0 -1
  147. package/dist/math-editor-toolbar.d.ts.map +0 -1
  148. package/dist/math-editor-toolbar.js.map +0 -1
  149. package/dist/math-editor.d.ts.map +0 -1
  150. package/dist/math-editor.js.map +0 -1
  151. package/dist/matrix.d.ts.map +0 -1
  152. package/dist/matrix.js.map +0 -1
  153. package/dist/model.d.ts.map +0 -1
  154. package/dist/model.js.map +0 -1
  155. package/dist/outputs.d.ts.map +0 -1
  156. package/dist/outputs.js.map +0 -1
  157. package/dist/range.d.ts.map +0 -1
  158. package/dist/range.js.map +0 -1
  159. package/dist/react.d.ts.map +0 -1
  160. package/dist/react.js.map +0 -1
  161. package/dist/session.d.ts.map +0 -1
  162. package/dist/session.js.map +0 -1
  163. package/dist/solid.d.ts.map +0 -1
  164. package/dist/solid.js.map +0 -1
  165. package/dist/suggestions.d.ts.map +0 -1
  166. package/dist/suggestions.js.map +0 -1
  167. package/dist/svelte.d.ts.map +0 -1
  168. package/dist/svelte.js.map +0 -1
  169. package/dist/symbol-browser.d.ts.map +0 -1
  170. package/dist/symbol-browser.js.map +0 -1
  171. package/dist/symbols.d.ts.map +0 -1
  172. package/dist/symbols.js.map +0 -1
  173. package/dist/templates.d.ts.map +0 -1
  174. package/dist/templates.js.map +0 -1
  175. package/dist/tokens.d.ts.map +0 -1
  176. package/dist/tokens.js.map +0 -1
  177. package/dist/vue.d.ts.map +0 -1
  178. package/dist/vue.js.map +0 -1
  179. package/dist/web-component.d.ts.map +0 -1
  180. package/dist/web-component.js.map +0 -1
package/STYLING.md ADDED
@@ -0,0 +1,157 @@
1
+ # Styling and themes
2
+
3
+ The editor exposes CSS custom properties for presentation. Set them on a wrapper around an editor, or on a shared ancestor for multiple editors. This works with React, native JavaScript, the light-DOM Web Component, Vue, Svelte and Solid. No theme provider or additional runtime is required. Styling does not change the JSON model, exported LaTeX or undo history.
4
+
5
+ Import the package stylesheet once, then add your application stylesheet:
6
+
7
+ ```js
8
+ import '@barocss/math-editor/style.css';
9
+ import './math-theme.css';
10
+ ```
11
+
12
+ ```css
13
+ .product-math {
14
+ --me-font-size: 18px;
15
+ --me-accent: #6b4bb3;
16
+ --me-variable: #385ea1;
17
+ --me-number: #a65316;
18
+ --me-symbol: #8056ad;
19
+ --me-function: #147d77;
20
+ --me-surface-padding: 24px 16px;
21
+ --me-surface-min-height: 100px;
22
+ --me-toolbar-padding: 6px;
23
+ --me-control-radius: 4px;
24
+ }
25
+ ```
26
+
27
+ ```tsx
28
+ // Rich React editor. Native framework adapters use the same wrapper pattern.
29
+ <div className="product-math">
30
+ <MathEditor onChange={(document, latex) => save(document, latex)} />
31
+ </div>
32
+ ```
33
+
34
+ ```js
35
+ // Native JavaScript. The wrapper may already carry application theme variables.
36
+ const host = document.querySelector('.product-math');
37
+ const editor = mountMathEditor(host, { onChange: save });
38
+ host.style.setProperty('--me-font-size', '20px');
39
+ // On unmount:
40
+ editor.destroy();
41
+ ```
42
+
43
+ ```html
44
+ <!-- Register with defineMathEditor() first. This component uses light DOM. -->
45
+ <barocss-math-editor class="product-math" locale="en"></barocss-math-editor>
46
+ ```
47
+
48
+ Try the website's **Theme**, **Math size** and **Compact spacing** controls in the [playground](https://math-editor.barocss.com/#playground). These are application examples, not required library presets.
49
+
50
+ ## Public CSS variables
51
+
52
+ Unset variables preserve the existing default appearance. Some controls intentionally have slightly different default shades/radii; a supplied token unifies them. Set lengths such as the base font size in `px` or `rem` when the editor and its portal have different parents.
53
+
54
+ | Variable | Controls | Default behavior |
55
+ | --- | --- | --- |
56
+ | `--me-font-size` | Base formula text and relative script sizes | `22px` in standalone editors |
57
+ | `--me-ui-font-family` | Toolbars, menus and helper text | `system-ui, sans-serif` |
58
+ | `--me-text` | UI text and neutral math glyphs | Dark green/gray |
59
+ | `--me-muted` | Help text, line numbers, menu details | Muted gray/green |
60
+ | `--me-accent` | Buttons, active borders, matrix selection | Green |
61
+ | `--me-line` | Toolbar separators and control/menu borders | Pale gray/green |
62
+ | `--me-background` | Editor root | Transparent |
63
+ | `--me-panel-background` | Suggestion menus, symbol cards, inputs/selects in toolbars | White |
64
+ | `--me-toolbar-background` | Main toolbar | Transparent |
65
+ | `--me-subtle-background` | Symbol browser and grid tools | Pale neutral |
66
+ | `--me-hover-background` | Toolbar hover | Pale green |
67
+ | `--me-active-background` | Selected suggestion and active controls | Pale green |
68
+ | `--me-variable` | Variable tokens, including focused inputs | Blue |
69
+ | `--me-number` | Numeric tokens (`constant` token kind) | Brown/orange |
70
+ | `--me-symbol` | Symbol tokens | Purple |
71
+ | `--me-function` | Function/operator names and their legend | Teal |
72
+ | `--me-input-background` | Editing slot background | Context-specific slot tint |
73
+ | `--me-input-focus-background` | Focused/hovered slot background | Stronger context-specific tint |
74
+ | `--me-selection-background` | Text and structural range selection | Blue |
75
+ | `--me-selection-color` | Native selected input text | White |
76
+ | `--me-focus-color` | Keyboard focus indicator | Blue |
77
+ | `--me-radius` | Suggestion panel corners | `10px` |
78
+ | `--me-control-radius` | Buttons and toolbar fields | `4px`–`7px` depending on control |
79
+ | `--me-surface-padding` | Block editing area padding | `48px 32px` |
80
+ | `--me-inline-padding` | Native inline area padding | `2px 4px`; plugin compact fields `0 2px` |
81
+ | `--me-surface-min-height` | Block area minimum height | Standalone `190px`; plugin panels `60px` |
82
+ | `--me-toolbar-padding` | Main toolbar padding | `12px` |
83
+ | `--me-toolbar-gap` | Gap between main toolbar controls | `4px` |
84
+ | `--me-line-gap` | Gap between top-level equation rows | `18px` |
85
+ | `--me-menu-shadow` | Floating menu shadow | Soft dark shadow |
86
+ | `--me-menu-z-index` | Suggestion menu stacking level | `1000` |
87
+
88
+ Inline mode retains its content-driven minimum height. Explicit LaTeX sizing commands and specialty mathematical fonts retain their own metrics; the base-size token is not an image scaling API. Formula export/KaTeX rendering is owned by the consumer and does not inherit semantic editing colors automatically.
89
+
90
+ ## Dark and monochrome themes
91
+
92
+ Define a theme in your own stylesheet. Supply foregrounds and backgrounds together so active inputs, selection and menus remain readable.
93
+
94
+ ```css
95
+ .product-math[data-theme='dark'] {
96
+ color-scheme: dark;
97
+ --me-background: #18232c;
98
+ --me-text: #e5edf3;
99
+ --me-muted: #a4b5c2;
100
+ --me-line: #435565;
101
+ --me-panel-background: #202f3b;
102
+ --me-subtle-background: #243441;
103
+ --me-hover-background: #334857;
104
+ --me-active-background: #354e61;
105
+ --me-accent: #83d5bc;
106
+ --me-variable: #9ac7ff;
107
+ --me-number: #ffc28b;
108
+ --me-symbol: #d0b7ff;
109
+ --me-function: #7bddd5;
110
+ --me-input-background: #223444;
111
+ --me-input-focus-background: #334d63;
112
+ --me-selection-background: #436483;
113
+ --me-selection-color: #fff;
114
+ --me-focus-color: #9ac7ff;
115
+ }
116
+
117
+ .product-math[data-theme='mono'] {
118
+ --me-variable: #303740;
119
+ --me-number: #303740;
120
+ --me-symbol: #303740;
121
+ --me-function: #303740;
122
+ --me-input-background: #f1f2f4;
123
+ --me-input-focus-background: #e0e4e9;
124
+ }
125
+ ```
126
+
127
+ Change `data-theme`, an ancestor class, or inline custom properties without remounting. The semantic model remains intact even when all token colors match.
128
+
129
+ ## Suggestions, separate toolbars and iframes
130
+
131
+ Suggestion and selection-wrapping menus usually mount under `document.body` or the nearest dialog. Both renderers copy the owning editor's resolved presentation tokens to these menus. Two editors can use different themes without applying a global `.me-suggestion-panel` rule. Ancestor attribute changes, window resizing and preferred color-scheme changes refresh the menu theme. Dynamic stylesheet replacement without those events is picked up on the next menu render/open; prefer changing a scoped class or custom property.
132
+
133
+ Host integrations keep menus inside the math node's event boundary. They use the same glyphs, labels and details as the main React editor. Pointer movement highlights candidates; a stationary pointer does not replace keyboard choices when the list scrolls or redraws. Menu text does not inherit paragraph alignment or letter spacing. Ordinary scrolling text areas do not reduce the fixed menu's height; dialogs and clipping containers that establish a local containing block still constrain it. In a short dialog, the native menu hides its guidance before reducing the space for selectable rows.
134
+
135
+ An independently mounted toolbar inherits from **its own host**. Put it under the same themed ancestor or apply the same theme class to the editor and toolbar containers. A toolbar shared between different themes does not automatically switch to the selected editor's theme.
136
+
137
+ CSS does not cross document boundaries. For a TinyMCE classic iframe, load both core and plugin stylesheets using `content_css` and load your theme CSS into that iframe too. Apply the theme to its body or a wrapper within that document. Style a shared toolbar outside the iframe separately. The menu bridge uses the editor's owner document; it does not copy parent-page CSS into an iframe. See [TinyMCE integration](../math-editor-integrations/docs/TINYMCE.md).
138
+
139
+ ## Editor plugin chrome
140
+
141
+ Import the chosen plugin's `style.css` alongside the core CSS. Shared plugin panels, Apply/Cancel buttons, size controls and hover states use the same `--me-*` palette. Additional host chrome variables are:
142
+
143
+ | Variable | Controls |
144
+ | --- | --- |
145
+ | `--bme-inline-background` | Background behind an in-place math draft |
146
+ | `--bme-action-color` | Text on the primary Apply button (default white) |
147
+ | `--bme-error-color` | Draft validation messages |
148
+
149
+ Formula size in host integrations is a **document attribute**: the plugin measures surrounding prose and combines it with the formula's saved percentage. Use the plugin's `fontSize` attribute/size controls for persisted math size. The internal `--bme-font-size` is computed from that value; setting a standalone `--me-font-size` on a wrapper does not override persisted host sizing.
150
+
151
+ For dark plugin fields, also set `--bme-inline-background: #223444` and choose a readable `--bme-action-color` for your accent. Themes style editor UI, not the surrounding host editor's own menu bar or rendered formula HTML.
152
+
153
+ ## Custom toolbar and layout
154
+
155
+ Use `toolbar: false` for editor-only embedding, a list of structure kinds to limit commands, or `toolbarMaxItems` for overflow. A completely custom toolbar can call `session.execute(...)`. See [embedding](EMBEDDING.md) and the [session API](API-SESSION.md).
156
+
157
+ Treat internal `.me-*` and `.bme-*` layout selectors as implementation details. Prefer these variables over overriding fractions, scripts, radical paths, integral glyphs, or input positioning: those metrics are coordinated for editing and KaTeX comparison. Theme changes should be checked with keyboard focus, selections, suggestions and tall nested formulas, as well as at rest.
package/SUPPORT.md CHANGED
@@ -46,7 +46,7 @@ See the [LaTeX editing guide](LATEX-GUIDE.md) for input examples and prioritized
46
46
  | Product | Lower limit, upper limit, body | `\prod_{i=1}^{n}x` | No evaluation |
47
47
  | Triple / contour integrals | Three editable slots; side bounds or explicit limits | `\iiint`, `\oint` | No computation; `\nolimits` supported in workspace |
48
48
  | Integral | Lower limit, upper limit, integrand | `\int_{a}^{b}x` | Differential entered as text; no integration engine |
49
- | Matrices / vectors | Editable nested cells; row/column changes | `matrix`, `pmatrix`, `bmatrix`, `Bmatrix`, `vmatrix`, `Vmatrix` | 1–20 rows and columns; no rectangular cell clipboard |
49
+ | Matrices / vectors | Editable nested cells; row/column changes | `matrix`, `pmatrix`, `bmatrix`, `Bmatrix`, `vmatrix`, `Vmatrix` | 1–20 rows and columns; workspace rectangular clipboard and full-matrix transpose |
50
50
  | Aligned equations | Two columns, left side right-aligned and right side left-aligned | `aligned` | 1–20 rows, one alignment point; type the relation explicitly |
51
51
  | Cases | Expression and condition columns, left brace | `cases` | 1–20 rows; conditions accept math and literal text nodes |
52
52
  | Multiple document lines | Split/join top-level rows | `gathered` | No wrapping several document lines into one structure |
@@ -56,17 +56,19 @@ See the [LaTeX editing guide](LATEX-GUIDE.md) for input examples and prioritized
56
56
  | Area | Supported | Remaining boundary |
57
57
  |---|---|---|
58
58
  | Languages | Korean and English UI, demo, accessible names, hints and suggestions | Custom host labels require host translation |
59
+ | Recent / favorites | Shared UI preference store for known symbols/templates, up to 12 recent and 100 favorites | Optional host persistence; no effect on formula JSON or Undo |
60
+ | Presentation changes | Fence type, fraction/binomial size and operator limits from the contextual toolbar | Existing notation only; no arbitrary fonts or dimensions |
59
61
  | Discovery | All-symbol grid with English/Korean/glyph/LaTeX search, click to insert | Scroll through the complete 117-symbol catalog |
60
62
  | Line numbers | UI gutter for multiple top-level rows, optional prop | Excluded from math output and clipboard |
61
63
  | Aliases | English and Korean in both locales; longest symbolic trigger first | Substring matching with exact matches first; no natural-language parsing |
62
64
  | Native input | Active token only; composition draft held stable | Real OS IME validation deferred |
63
65
  | Roles | Variables, numeric literals, symbols, spaces | Lexical colors are not semantic declarations |
64
- | Selection | Partial text, drag across tokens/structures, whole document in preview | No keyboard Shift-arrow model expansion or rectangular cell selection |
66
+ | Selection | Partial text, drag across tokens/structures, whole document in preview | Shift+arrow model ranges and workspace rectangular cell selection; touch/OS clipboard validation remains |
65
67
  | Wrapping | Fraction, root, power, subscript, parentheses, brackets, absolute value | One balanced row; cross-slot range expands to common structure |
66
- | Clipboard | Structured internal fragment plus LaTeX text; cut/paste replacement | External LaTeX is literal text; nested multiline paste rejected |
67
- | History | Undo/redo of model changes and caret, up to 100 prior states | No shared host undo adapter yet |
68
+ | Clipboard | Structured internal fragment plus LaTeX text; cut/paste replacement | Ordinary paste is literal; explicit LaTeX insertion parses at the caret/range; nested multiline paste rejected |
69
+ | History | Undo/redo of model changes and caret, up to 100 prior states | Shared host adapters commit one formula edit; per-host history support varies |
68
70
  | Deletion | Text boundaries, line join, structure unwrap, two-step filled-grid deletion | General structural deletion rules are still limited |
69
- | Serialization | Version-1 JSON document and change callback | No persistence service, saved-document validator or migrations |
71
+ | Serialization | Version-1 JSON document and change callback | `parseMathDocument` validates shape and IDs; persistence and future version migrations remain host work |
70
72
  | Preview | Demo-only KaTeX view | Library renders its own editable presentation |
71
73
  | Accessibility | Localized labels, combobox suggestions, keyboard slot navigation | Not a complete mathematical screen-reader implementation |
72
74
  | Computation | None | No solving, simplification, units engine or collaboration |
@@ -75,9 +77,9 @@ See the [LaTeX editing guide](LATEX-GUIDE.md) for input examples and prioritized
75
77
 
76
78
  | Template / preset | Sizes | Discovery | Behavior |
77
79
  |---|---|---|---|
78
- | Empty matrix | 2×2, 3×3, 4×4 | `matrix`, `행`, `행렬`; matrix menu | Editable empty cells |
79
- | Identity matrix | 2×2, 3×3, 4×4 | `identity`, `단위`, `단위행렬`; matrix menu | Diagonal 1, other entries 0 |
80
- | Zero matrix | 2×2, 3×3, 4×4 | `zero`, `영행렬`; template menu | All entries 0, editable |
80
+ | Empty matrix | 1–20 rows × 1–20 columns via size shortcuts; 2×2/3×3/4×4 menu presets | `matrix`, `행`, `행렬`; matrix menu | Editable empty cells |
81
+ | Identity matrix | Square sizes 1–20 via size shortcuts; 2×2/3×3/4×4 menu presets | `identity`, `단위`, `단위행렬`; matrix menu | Diagonal 1, other entries 0 |
82
+ | Zero matrix | 1–20 rows × 1–20 columns via size shortcuts; 2×2/3×3/4×4 templates | `zero`, `영행렬`; template menu | All entries 0, editable |
81
83
  | Column vector | 2×1, 3×1 | `vector`, `column`, `벡터`, `열벡터`; template menu | Empty editable column; existing generic matrix candidates may also match |
82
84
  | Quadratic formula | One expression | `quadratic`, `근의공식`; template menu | `x=(-b±√(b²−4ac))/(2a)` using fraction/root/power nodes; assumes a≠0 mathematically, does not enforce it |
83
85
  | Pythagorean theorem | One expression | `pythagorean`, `피타고라스`; template menu | `a²+b²=c²` using editable power nodes |
@@ -212,7 +214,7 @@ Catalog size: **117 symbols**.
212
214
 
213
215
  ## Renderer availability
214
216
 
215
- This formula catalog describes the shared model and the existing rich React editor. New native DOM/framework surfaces share the notation model but have a smaller interaction UI. See the [renderer parity table](ADAPTERS.md#current-renderer-parity) before choosing an adapter.
217
+ This formula catalog describes the shared model and the existing rich React editor. Native DOM/framework surfaces share the notation model, token editing, discovery and presentation utilities; host integration policies remain separate. See the [renderer parity table](ADAPTERS.md#current-renderer-parity) before choosing an adapter.
216
218
 
217
219
  ### Combined scripts (workspace)
218
220
 
@@ -253,12 +255,12 @@ Type `quad` / `qquad` (or `간격` / `큰간격`) for an explicit gap. The caret
253
255
 
254
256
  Select an expression and choose a fence or annotation in the regular suggestion menu. Fences wrap the expression and resume after it. Annotations keep the selected expression in the body and focus the empty annotation slot. Nested fractions, scripts and other editable structures are allowed in both annotation slots. Backspace after a structure removes its wrapper while preserving its content; Undo restores it.
255
257
 
256
- All structured fences scale with their contents. Short angle notation normalizes to scalable `\left\langle…\right\rangle` on export. Supported physical ends are `(`, `)`, `[`, `]`, `{`, `}`, `⟨`, `⟩`, `|` and `.`. Plain punctuation stays literal unless imported with a structural command. Double norm bars, `\big`/`\Big`, `\overbrace` and `\underbrace` remain unsupported.
258
+ All structured fences scale with their contents. Short angle notation normalizes to scalable `\left\langle…\right\rangle` on export. Supported physical ends are `(`, `)`, `[`, `]`, `{`, `}`, `⟨`, `⟩`, `|` and `.`. Plain punctuation stays literal unless imported with a structural command. Double norm bars and brace annotations are supported. Explicit `\big`/`\Big` sizing remains unsupported.
257
259
 
258
260
 
259
261
  ### Double integrals (workspace)
260
262
 
261
- `\iint_R f(x,y)` imports as `doubleIntegral` with exactly three slots: `[lower/domain, upper, body]`, matching integral navigation. Missing bounds remain editable empty slots. `\iint\limits` preserves the explicit placement directive. Export uses `\iint_{lower}^{upper}{body}`. Search `iint`, `이중적분` or `∬` to insert; Tab visits the domain, upper bound and body in order. The reported expression with `\rm dx \rm dy`, `\xi`, `\eta` and `\text{Area}` is covered by import/round-trip and browser editing fixtures. This is notation support, not numerical integration. Triple and contour integrals remain outside this addition.
263
+ `\iint_R f(x,y)` imports as `doubleIntegral` with exactly three slots: `[lower/domain, upper, body]`, matching integral navigation. Missing bounds remain editable empty slots. `\iint\limits` preserves the explicit placement directive. Export uses `\iint_{lower}^{upper}{body}`. Search `iint`, `이중적분` or `∬` to insert; Tab visits the domain, upper bound and body in order. The reported expression with `\rm dx \rm dy`, `\xi`, `\eta` and `\text{Area}` is covered by import/round-trip and browser editing fixtures. This is notation support, not numerical integration. Triple and contour integrals are also supported.
262
264
 
263
265
  Fine spacing: `\,`, `\:`, `\;`, `\!` are preserved as zero-slot structures. Positive spacing expands and negative thin spacing contracts the next boundary; these are not editable text spaces.
264
266
 
@@ -276,6 +278,16 @@ Fine spacing: `\,`, `\:`, `\;`, `\!` are preserved as zero-slot structures. Posi
276
278
 
277
279
  ## Keyboard ranges and brace discovery (0.2.1)
278
280
 
279
- Shift+Left/Right extends or shrinks the same model range used by dragging. Structures are crossed as balanced units; copied and deleted ranges use existing model normalization. Shift+Up/Down extends to an adjacent top-level document line, using a logical text offset rather than pixel-based column matching; grid-cell rectangular selection is not added. Copy, cut, wrapping, deletion and Undo use the existing range behavior. IME composition and modifier shortcuts retain their existing handling.
281
+ Shift+Left/Right extends or shrinks the same model range used by dragging. Structures are crossed as balanced units; copied and deleted ranges use existing model normalization. Shift+Up/Down extends to an adjacent top-level document line, using a logical text offset rather than pixel-based column matching; an active matrix-cell selection instead uses Shift+arrows to move its rectangular focus corner. Copy, cut, wrapping, deletion and Undo use the existing range behavior. IME composition and modifier shortcuts retain their existing handling.
282
+
283
+ After a model selection, plain Left/Up restores the caret at its document-ordered
284
+ start; Right/Down restores it at the end. The same rule applies to reverse drags
285
+ and Shift+arrow selections, without consuming another character or changing the
286
+ formula. Alt+Up/Down browses the wrapping suggestions while retaining the range;
287
+ Enter applies a candidate. Ordinary input suggestions still use Up/Down.
280
288
 
281
289
  Typing `{` offers both paired braces and cases. Paired braces remain the first candidate; choose Cases explicitly to insert its editable grid.
290
+
291
+ ## Presentation customization
292
+
293
+ CSS variables support scoped palettes, token colors, slot focus/selection, toolbar density and menu chrome in both renderers and all framework adapters. Portaled suggestions follow their owning editor. See [Styling & themes](STYLING.md); host integrations retain their own persisted formula-size attributes.
package/VALIDATION.md CHANGED
@@ -1,6 +1,43 @@
1
1
  # Validation report
2
2
 
3
- ## Current release status0.2.0
3
+ ## Styling and themesworkspace, 2026-09-09
4
+
5
+ Release browser regression: all 385 existing Chromium cases passed across the
6
+ full run and a focused rerun. Three stale expectations were updated for locale
7
+ query parameters, the explicit toolbar overflow button and Alt+arrow wrapping
8
+ navigation; all 26 cases in those three files passed on rerun. Shared integration
9
+ unit tests: 52 passing. No notation/model changes were needed for theming.
10
+
11
+ `apps/math-integrations/tests/style-customization-check.js` passed in Chromium:
12
+ independent light/dark instances, resolved host `var()` aliases, isolation from
13
+ unrelated portal-container variables, external toolbar styling, native iframe
14
+ menus, rich React suggestions/selection menus, and live theme switches preserving
15
+ input focus, document state and history. The site controls and styling reader
16
+ were also checked in Korean; English controls are covered by the fixture.
17
+
18
+ `tinymce-inline-check.js` additionally passed palette/action styling and the
19
+ existing typing, Apply/Cancel, serialization, history and cleanup cases in both
20
+ inline and classic iframe TinyMCE. The Apply control now has an explicit class,
21
+ so prepended size controls cannot prevent its primary style. These checks do not
22
+ certify arbitrary customer CSS palettes or browser/assistive-technology contrast.
23
+
24
+
25
+ ## Selection collapse — workspace, 2026-09-09
26
+
27
+ Core unit regression: 503 tests in 31 files. The added cases cover ordered edges
28
+ for forward/reversed structural ranges, partial-text and collapsed ranges,
29
+ absent ranges/non-arrow keys and unchanged source. Plain arrows now restore
30
+ a caret from model selection in both renderers; Alt+Up/Down retains a range for
31
+ wrapping-suggestion navigation.
32
+
33
+ Real Chromium checks in `apps/math-integrations/tests/selection-collapse-check.js`
34
+ passed for rich React, including subsequent typing and fraction-boundary collapse.
35
+ `tinymce-inline-check.js` passed all four plain arrows and subsequent typing in
36
+ both inline TinyMCE and its classic iframe. Native selection/toolbar, shared iframe
37
+ and all nine matrix host regressions also passed. Wider browser and OS IME checks
38
+ remain open. This is workspace evidence, not a new published package version.
39
+
40
+ ## Current release status — 0.2.1
4
41
 
5
42
  The dated entries below are historical verification records, not current release availability claims. Note inline editing is resolved according to the user; this release does not change or deploy Note. Math-editor release checks and site validation are recorded separately at the end of this document.
6
43
 
@@ -43,14 +80,68 @@ The in-app browser also verified the complete English demo/editor UI, quadratic-
43
80
  - Real OS Korean IME testing is deferred at the user's request. Synthetic composition events verify guarding and draft handling only.
44
81
  - Automated browser coverage is Chromium on macOS. Narrow viewport checks are not mobile-browser or touch validation.
45
82
  - Full screen-reader math navigation, high contrast, and every theme are not certified.
46
- - There is no saved-document validator, persistence/restart-recovery test, migration path or host-wide undo integration.
47
- - Rectangular matrix selections, keyboard model range extension, general LaTeX import and symbolic computation are not implemented.
83
+ - Saved-document validation, bounded LaTeX import and keyboard model ranges are implemented; model-version migration and recovery remain open. Host persistence/Undo evidence is recorded separately in the integration validation guide.
84
+ - Matrix rectangles and transpose are implemented in the workspace; full TeX interpretation and symbolic computation are not implemented.
48
85
  - Deep nesting and very large expressions have no agreed performance budget yet.
49
86
 
50
87
  ## Integration gates
51
88
 
52
89
  Before shipping inside a host product, test actual input methods and target browsers, establish a single undo owner, validate saved documents, verify focus across host/math boundaries, and define recoverable clipboard/import failures. Every new structure must have localized labels, deterministic transformations, one-step structural undo, browser interaction coverage and supported LaTeX output.
53
90
 
91
+ ## Matrix cell ranges and transpose — 2026-09-09 workspace
92
+
93
+ - 498 core tests pass, including 16 cell-rectangle/model/history cases and a grid
94
+ hit-test regression for tall fractions. Selection snapshots stay isolated;
95
+ invalid shapes and oversized pastes preserve the entire document/history.
96
+ - `apps/math-integrations/tests/matrix-range-check.js` passes in both rich React
97
+ and the native renderer with zero page errors. It covers active-input drag,
98
+ reversed corners, keyboard extension/shrinking, Shift+click, exact nested-cell
99
+ clipboard payloads, mismatched paste rejection, paste/cut/clear/typing Undo,
100
+ anchored TSV growth, full transpose and resumed nested-denominator editing.
101
+ - The same exported transposed matrix was inspected in both editors and KaTeX.
102
+ Screenshots: `output/playwright/matrix-range-native-katex.png` and
103
+ `output/playwright/matrix-range-react-katex.png`. Cell order and nested structure
104
+ match; editing retains its existing spacing and caret targets.
105
+ - Pointer hit testing uses row and column tracks independently once a cell range
106
+ starts. A tall fraction no longer attracts the pointer away from a neighboring
107
+ cell during edge scrolling. Pointer focus uses `preventScroll`, and the React
108
+ selection hint is below the expression to avoid moving it mid-drag.
109
+ - Clipboard checks dispatch ClipboardEvent with DataTransfer; they do not certify
110
+ system clipboard transfer to other applications. OS IME, touch, Safari/Firefox
111
+ and screen-reader coverage remain open. No new LaTeX syntax or model version was
112
+ introduced; neither npm publication nor site deployment is part of this run.
113
+
114
+ ## Native controls and precise selection — 2026-09-09 workspace
115
+
116
+ The native surface now exposes symbol search, templates and matrix presets behind
117
+ More tools, plus contextual row/column/delimiter controls. Selected passive text
118
+ is highlighted at its exact endpoints. Pointer drags can start in the live input
119
+ and extend through structures while ordinary within-input selection stays native.
120
+
121
+ - The core suite passes 481 tests, including toolbar catalog restrictions,
122
+ partial-text intersections, Shift-drag anchors, selection-menu dismissal and
123
+ suggestion placement around reserved host controls.
124
+ - `apps/math-integrations/tests/native-parity-check.js` passes in Chromium with
125
+ zero page errors: exact partial highlights, native within-input replacement,
126
+ cross-fraction selection in both directions, Shift-drag forward/backward anchors,
127
+ shrinking the range back, replacement and Undo, symbol insertion into selected
128
+ text, editable templates, matrix edits, filtered/hidden toolbars and teardown.
129
+ - Selection Escape checks preserve the model/native range on the first Escape,
130
+ reopen suggestions for a changed selection, and notify host Cancel on the second.
131
+ - The browser clipboard check dispatches a ClipboardEvent with DataTransfer and
132
+ verifies both exact LaTeX endpoints and the structured MIME payload. This is
133
+ handler validation, not an OS clipboard compatibility claim.
134
+ - Synthetic formula composition disables toolbar mutations and restores them on
135
+ composition end. Real OS IME testing remains deferred.
136
+ - The existing fraction fixture is shown alongside its KaTeX output in
137
+ `output/playwright/native-parity-editor-katex.png`; this addition changes
138
+ controls and selection presentation, not supported notation or glyph geometry.
139
+
140
+ These are workspace changes awaiting release. Whole logical-run active inputs,
141
+ one active-run role color, additional
142
+ complete locale packs and broader browser/accessibility certification remain open.
143
+ Host-specific browser findings are recorded in the integration validation guide.
144
+
54
145
  ## Line numbers and symbol discovery
55
146
 
56
147
  Three browser tests verify the UI-only line gutter through split/join/undo, its exclusion from LaTeX and model clipboard, the complete 90-symbol grid, bilingual search, insertion before typing, range replacement, undo, Escape and clipboard isolation in the search field. A native Home key did not move the caret in the macOS test environment; the test uses the supported Left arrow to reach the line start. The in-app browser visually confirmed the full symbol grid with names and search focus.
@@ -63,7 +154,12 @@ Three browser tests verify the UI-only line gutter through split/join/undo, its
63
154
  - A packed tarball was extracted into a temporary directory. Five entry points (`core`, `dom`, `web-component`, `vue`, `svelte`) imported without framework peers or browser globals; main/React/Solid entries imported after installing links to only their relevant local peers. All eight JS export paths and declared CSS/type targets exist. This is a local packaging smoke test, not publication or an exhaustive downstream TypeScript/bundler matrix.
64
155
  - The in-app browser visually verified the adapter lab and actual pure-JS `x/ → ↓ → Enter → 2` input, producing `\frac{x}{2}`. Inline mode displays without the block editor's large surface and toolbar.
65
156
 
66
- Remaining adapter gates: exact range highlighting, active-input cross-structure dragging, token-level edit colors, composition preview parity, native all-symbol/context toolbars, reactive-option/version matrix and accessibility/browser coverage. Native `MathEditorSurface` is not advertised as complete rich React parity. OS IME testing remains deferred.
157
+ Updated 2026-09-09: exact range highlighting, active-input cross-structure dragging
158
+ and native symbol/template/grid controls are implemented in the workspace.
159
+ Remaining adapter gates include token-level active input/colors, composition
160
+ preview parity, framework/version coverage and accessibility/browser coverage.
161
+ Native `MathEditorSurface` is not advertised as complete rich React parity.
162
+ OS IME testing remains deferred.
67
163
 
68
164
  ## Composition, source readability and JSON locales — 2026-09-08
69
165
 
@@ -258,3 +354,16 @@ Package build and 388 unit tests passed. Built-in locale tests reject misplaced
258
354
  ## 0.2.1 release verification
259
355
 
260
356
  Formatting, types, 390 unit tests and all eight packed entry points passed. Thirty-nine related Chromium cases passed for keyboard and drag selection, clipboard, wrapping, tokens, brace/cases discovery, preview and bundled locales. Website PNG download/copy checks validate dimensions and nonblank raster pixels. Long-formula fitting and transparent alpha were checked separately. Image generation is a demo dependency, not a library API.
357
+
358
+ ## Editing utilities — workspace, 2026-09-09
359
+
360
+ Core unit regression: **548 tests**. Shared integrations: **52 tests**. Core, integration package and both demo applications pass TypeScript checks. The main demo documentation/site build and the integration demo production build pass. The integration demo retains its existing large host-SDK chunk warnings.
361
+
362
+ - `token-paste-check.js`: Vue and rich React pass continuous token typing, middle insertion, boundary deletion, range replacement through explicit LaTeX paste, atomic diagnostics, continued typing and Undo.
363
+ - `quick-presentation-check.js`: Vue, rich React and Quill pass symbol/template favorites, recent items, insertion focus, bracket/size/limit changes and Undo. Editor views and the live KaTeX output were visually compared.
364
+ - `host-composition-check.js`: 16 supported inline/block combinations across nine host demos preserve browser preedit, commit/cancel and subsequent typing without per-key refocusing.
365
+ - `latex-paste-host-check.js`: the same 16 combinations keep Ctrl/Cmd+Enter inside the paste form. Inserted fractions remain local drafts, typing resumes, and Cancel preserves host serialization.
366
+ - `native-parity-check.js`: exact text and cross-structure selection, Shift-drag, shrinkback, structured copy, discovery, grid controls, restrictions and teardown pass. Its coordinate helper now maps logical text across token spans.
367
+ - Quill composition and typing regression fixtures pass, including read-only recovery, host history and matrix-cell entry.
368
+
369
+ This is local Chromium evidence. OS IME, Safari/Firefox, installed WordPress and cross-application system clipboard checks were not repeated. No new mathematical grammar or numerical behavior is introduced. Release artifacts prepared before these changes require regeneration; nothing was published by this work.
package/dist/core.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export * from './model.js';
2
2
  export * from './matrix.js';
3
+ export * from './matrix-range.js';
3
4
  export * from './lines.js';
5
+ export * from './vertical-navigation.js';
4
6
  export * from './range.js';
5
7
  export * from './tokens.js';
6
8
  export * from './symbols.js';
@@ -10,4 +12,6 @@ export * from './session.js';
10
12
  export * from './i18n.js';
11
13
  export * from './latex.js';
12
14
  export * from './document-codec.js';
13
- //# sourceMappingURL=core.d.ts.map
15
+ export * from './latex-insertion.js';
16
+ export * from './preferences.js';
17
+ export * from './presentation.js';
package/dist/core.js CHANGED
@@ -1,6 +1,8 @@
1
1
  export * from './model.js';
2
2
  export * from './matrix.js';
3
+ export * from './matrix-range.js';
3
4
  export * from './lines.js';
5
+ export * from './vertical-navigation.js';
4
6
  export * from './range.js';
5
7
  export * from './tokens.js';
6
8
  export * from './symbols.js';
@@ -10,4 +12,6 @@ export * from './session.js';
10
12
  export * from './i18n.js';
11
13
  export * from './latex.js';
12
14
  export * from './document-codec.js';
13
- //# sourceMappingURL=core.js.map
15
+ export * from './latex-insertion.js';
16
+ export * from './preferences.js';
17
+ export * from './presentation.js';
@@ -1,4 +1,3 @@
1
1
  import { type MathDocument } from './model.js';
2
2
  /** Validate persisted JSON before passing it to a renderer. Reject duplicate caret addresses. */
3
3
  export declare function parseMathDocument(source: string): MathDocument | undefined;
4
- //# sourceMappingURL=document-codec.d.ts.map
@@ -34,4 +34,3 @@ export function parseMathDocument(source) {
34
34
  return;
35
35
  }
36
36
  }
37
- //# sourceMappingURL=document-codec.js.map
@@ -0,0 +1,3 @@
1
+ import type { MathCaretGeometry } from '../vertical-navigation.js';
2
+ /** Measure existing mirrors, including React and native DOM token spans. */
3
+ export declare function renderedCaretGeometry(surface: HTMLElement): MathCaretGeometry;
@@ -0,0 +1,41 @@
1
+ /** Measure existing mirrors, including React and native DOM token spans. */
2
+ export function renderedCaretGeometry(surface) {
3
+ const runs = new Map();
4
+ for (const run of surface.querySelectorAll('.me-run[data-run-id]')) {
5
+ const id = run.dataset.runId;
6
+ const group = runs.get(id) ?? [];
7
+ group.push(run);
8
+ runs.set(id, group);
9
+ }
10
+ return ({ id, offset, affinity }) => {
11
+ const group = runs.get(id);
12
+ if (!group)
13
+ return;
14
+ const contains = (run) => {
15
+ const start = Number(run.dataset.tokenStart ?? 0);
16
+ return offset >= start && offset <= start + (run.dataset.value?.length ?? 0);
17
+ };
18
+ const candidates = group.filter(contains);
19
+ // An explicit side must win over active-input state when measuring target candidates.
20
+ const run = affinity
21
+ ? affinity === 'forward'
22
+ ? candidates.at(-1)
23
+ : candidates[0]
24
+ : candidates.find((item) => item.querySelector('.me-input')) ?? candidates[0];
25
+ const mirror = run?.querySelector('.me-measure');
26
+ if (!run || !mirror)
27
+ return;
28
+ const value = run.dataset.value ?? '';
29
+ if (value && mirror.firstChild?.nodeType === 3) {
30
+ const range = surface.ownerDocument.createRange();
31
+ range.setStart(mirror.firstChild, offset - Number(run.dataset.tokenStart ?? 0));
32
+ range.collapse(true);
33
+ const rect = range.getBoundingClientRect();
34
+ if (rect.height)
35
+ return { x: rect.left, y: rect.top + rect.height / 2 };
36
+ }
37
+ const rect = mirror.getBoundingClientRect();
38
+ if (!value && rect.height)
39
+ return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
40
+ };
41
+ }
@@ -1,3 +1,2 @@
1
1
  /** Shared native element factory. Text is assigned with textContent, never parsed as HTML. */
2
- export declare const element: <K extends keyof HTMLElementTagNameMap>(tag: K, className?: string, text?: string) => HTMLElementTagNameMap[K];
3
- //# sourceMappingURL=elements.d.ts.map
2
+ export declare const elementFactory: (doc: Document) => <K extends keyof HTMLElementTagNameMap>(tag: K, className?: string, text?: string) => HTMLElementTagNameMap[K];
@@ -1,9 +1,8 @@
1
1
  /** Shared native element factory. Text is assigned with textContent, never parsed as HTML. */
2
- export const element = (tag, className = '', text) => {
3
- const node = document.createElement(tag);
2
+ export const elementFactory = (doc) => (tag, className = '', text) => {
3
+ const node = doc.createElement(tag);
4
4
  node.className = className;
5
5
  if (text !== undefined)
6
6
  node.textContent = text;
7
7
  return node;
8
8
  };
9
- //# sourceMappingURL=elements.js.map
@@ -0,0 +1,10 @@
1
+ import { type MathLocale } from '../i18n.js';
2
+ import type { LatexParseResult } from '../latex.js';
3
+ /** Explicit, nonmodal paste UI. It never reads the OS clipboard without a user paste. */
4
+ export declare function mountLatexPanel(host: HTMLElement, options: {
5
+ locale: MathLocale;
6
+ onInsert(source: string): LatexParseResult;
7
+ onClose(): void;
8
+ }): {
9
+ destroy(): void;
10
+ };
@@ -0,0 +1,59 @@
1
+ import { elementFactory } from './elements.js';
2
+ import { translate } from '../i18n.js';
3
+ /** Explicit, nonmodal paste UI. It never reads the OS clipboard without a user paste. */
4
+ export function mountLatexPanel(host, options) {
5
+ const el = elementFactory(host.ownerDocument);
6
+ const t = (key) => translate(options.locale, key);
7
+ const panel = el('section', 'me-utility-panel');
8
+ panel.setAttribute('aria-label', t('clipboard.latexTitle'));
9
+ const label = el('label', '', t('clipboard.latexTitle'));
10
+ const source = el('textarea');
11
+ source.rows = 3;
12
+ source.placeholder = String.raw `\frac{a}{b} + x^2`;
13
+ source.spellcheck = false;
14
+ label.append(source);
15
+ const hint = el('p', '', t('clipboard.latexHint'));
16
+ const error = el('p', 'me-utility-error');
17
+ error.setAttribute('role', 'alert');
18
+ const actions = el('div', 'me-utility-actions');
19
+ const insert = el('button', '', t('clipboard.latexInsert'));
20
+ insert.type = 'button';
21
+ insert.onclick = () => {
22
+ const result = options.onInsert(source.value);
23
+ if (result.ok)
24
+ options.onClose();
25
+ else {
26
+ const diagnostic = result.diagnostics[0];
27
+ error.textContent = `${t('clipboard.latexError')} (${diagnostic.start + 1}): ${diagnostic.message}`;
28
+ source.focus();
29
+ source.setSelectionRange(diagnostic.start, diagnostic.end);
30
+ }
31
+ };
32
+ const close = el('button', '', t('common.close'));
33
+ close.type = 'button';
34
+ close.onclick = options.onClose;
35
+ actions.append(insert, close);
36
+ panel.append(label, hint, error, actions);
37
+ host.append(panel);
38
+ panel.addEventListener('keydown', (event) => {
39
+ event.stopPropagation();
40
+ if (event.isComposing)
41
+ return;
42
+ if (event.key === 'Escape') {
43
+ event.preventDefault();
44
+ options.onClose();
45
+ }
46
+ else if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
47
+ event.preventDefault();
48
+ insert.click();
49
+ }
50
+ });
51
+ for (const type of ['copy', 'cut', 'paste', 'input', 'beforeinput', 'keyup'])
52
+ panel.addEventListener(type, (event) => event.stopPropagation());
53
+ source.focus();
54
+ return {
55
+ destroy() {
56
+ panel.remove();
57
+ },
58
+ };
59
+ }
@@ -0,0 +1,11 @@
1
+ import type { MathMatrixRange } from '../matrix-range.js';
2
+ type CellRect = {
3
+ left: number;
4
+ right: number;
5
+ top: number;
6
+ bottom: number;
7
+ };
8
+ /** Grid tracks, rather than nearest glyphs, keep tall fractions from stealing adjacent cells. */
9
+ export declare function matrixCellAtPoint(columns: number, cells: CellRect[], x: number, y: number): number;
10
+ export declare function matrixPointerRange(surface: HTMLElement, range: MathMatrixRange, columns: number, x: number, y: number): MathMatrixRange;
11
+ export {};
@@ -0,0 +1,30 @@
1
+ /** Grid tracks, rather than nearest glyphs, keep tall fractions from stealing adjacent cells. */
2
+ export function matrixCellAtPoint(columns, cells, x, y) {
3
+ const nearest = (axis) => {
4
+ const count = axis === 'row' ? cells.length / columns : columns;
5
+ let chosen = 0, distance = Infinity;
6
+ for (let track = 0; track < count; track++) {
7
+ const group = cells.filter((_, i) => (axis === 'row' ? Math.floor(i / columns) : i % columns) === track);
8
+ const start = Math.min(...group.map((cell) => (axis === 'row' ? cell.top : cell.left)));
9
+ const end = Math.max(...group.map((cell) => (axis === 'row' ? cell.bottom : cell.right)));
10
+ const point = axis === 'row' ? y : x;
11
+ const next = Math.max(start - point, 0, point - end);
12
+ if (next < distance) {
13
+ chosen = track;
14
+ distance = next;
15
+ }
16
+ }
17
+ return chosen;
18
+ };
19
+ return nearest('row') * columns + nearest('column');
20
+ }
21
+ export function matrixPointerRange(surface, range, columns, x, y) {
22
+ const grid = [...surface.querySelectorAll('.me-matrix')].find((node) => node.dataset.matrixId === range.matrixId);
23
+ const cells = grid?.querySelectorAll(':scope > .me-matrix-grid > .me-matrix-cell');
24
+ if (!cells?.length)
25
+ return range;
26
+ return {
27
+ ...range,
28
+ focus: matrixCellAtPoint(columns, [...cells].map((cell) => cell.getBoundingClientRect()), x, y),
29
+ };
30
+ }