@barocss/math-editor 0.1.0 → 0.2.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.
- package/ADAPTERS.md +31 -2
- package/API-JAVASCRIPT.md +157 -0
- package/API-REACT.md +202 -0
- package/API-SESSION.md +163 -0
- package/API-SOLID.md +90 -0
- package/API-SVELTE.md +83 -0
- package/API-VUE.md +104 -0
- package/API-WEB-COMPONENT.md +128 -0
- package/CHANGELOG.md +21 -0
- package/EMBEDDING.md +7 -0
- package/IMPLEMENTATION.md +132 -1
- package/JSON-MODEL.md +441 -0
- package/LATEX-GUIDE.md +254 -0
- package/LATEX-MODEL.md +290 -0
- package/LATEX-SCOPE.md +260 -4
- package/LOCALIZATION.md +7 -1
- package/README.md +97 -4
- package/RELEASING.md +1 -1
- package/ROADMAP.md +111 -16
- package/SUPPORT.md +134 -13
- package/SYMBOLS.md +33 -1
- package/VALIDATION.md +175 -0
- package/dist/core.d.ts +2 -0
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +2 -0
- package/dist/core.js.map +1 -1
- package/dist/document-codec.d.ts +4 -0
- package/dist/document-codec.d.ts.map +1 -0
- package/dist/document-codec.js +37 -0
- package/dist/document-codec.js.map +1 -0
- package/dist/dom/menu-position.d.ts +3 -0
- package/dist/dom/menu-position.d.ts.map +1 -0
- package/dist/dom/menu-position.js +44 -0
- package/dist/dom/menu-position.js.map +1 -0
- package/dist/dom/toolbar.d.ts +2 -0
- package/dist/dom/toolbar.d.ts.map +1 -1
- package/dist/dom/toolbar.js +16 -1
- package/dist/dom/toolbar.js.map +1 -1
- package/dist/dom.d.ts +1 -0
- package/dist/dom.d.ts.map +1 -1
- package/dist/dom.js +153 -24
- package/dist/dom.js.map +1 -1
- package/dist/editor-labels.d.ts.map +1 -1
- package/dist/editor-labels.js +42 -0
- package/dist/editor-labels.js.map +1 -1
- package/dist/fences.d.ts +11 -0
- package/dist/fences.d.ts.map +1 -0
- package/dist/fences.js +21 -0
- package/dist/fences.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/latex.d.ts +26 -0
- package/dist/latex.d.ts.map +1 -0
- package/dist/latex.js +689 -0
- package/dist/latex.js.map +1 -0
- package/dist/locales/en.js +109 -2
- package/dist/locales/en.json +109 -2
- package/dist/locales/ko.js +121 -3
- package/dist/locales/ko.json +121 -3
- package/dist/math-editor-toolbar.d.ts +5 -2
- package/dist/math-editor-toolbar.d.ts.map +1 -1
- package/dist/math-editor-toolbar.js +10 -6
- package/dist/math-editor-toolbar.js.map +1 -1
- package/dist/math-editor.d.ts +24 -4
- package/dist/math-editor.d.ts.map +1 -1
- package/dist/math-editor.js +236 -40
- package/dist/math-editor.js.map +1 -1
- package/dist/model.d.ts +10 -2
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +262 -9
- package/dist/model.js.map +1 -1
- package/dist/range.d.ts +3 -1
- package/dist/range.d.ts.map +1 -1
- package/dist/range.js +162 -6
- package/dist/range.js.map +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/session.d.ts +3 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +9 -1
- package/dist/session.js.map +1 -1
- package/dist/suggestions.d.ts +4 -0
- package/dist/suggestions.d.ts.map +1 -1
- package/dist/suggestions.js +161 -22
- package/dist/suggestions.js.map +1 -1
- package/dist/symbols.d.ts +1 -1
- package/dist/symbols.d.ts.map +1 -1
- package/dist/symbols.js +56 -5
- package/dist/symbols.js.map +1 -1
- package/package.json +3 -2
- package/src/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/src/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/src/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/src/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/src/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/src/fonts/LICENSE-KaTeX.txt +21 -0
- package/src/style.css +868 -45
package/JSON-MODEL.md
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
# JSON model specification — version 1
|
|
2
|
+
|
|
3
|
+
This document defines the persisted `MathDocument` used by math-editor. The TypeScript declarations live in `src/model.ts`; external JSON is checked by `parseMathDocument` in `src/document-codec.ts`, using the structural validator in `src/range.ts`.
|
|
4
|
+
|
|
5
|
+
This is a **presentation tree**: it records how a formula is arranged and edited. It is not an algebraic expression tree. For example, `a+b` is text, not an `Add` operation. For syntax conversion, see [LaTeX → JSON mapping](LATEX-MODEL.md). This specification describes version 0.2.0, including validated JSON and bounded LaTeX import.
|
|
6
|
+
|
|
7
|
+
## 1. Document envelope
|
|
8
|
+
|
|
9
|
+
| Field | Required | JSON type | Contract |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| `version` | Yes | Number | Exactly `1`, not the package version and not the string `"1"` |
|
|
12
|
+
| `root` | Yes | `MathRow` object | The first top-level formula line; always exists, even for an empty formula |
|
|
13
|
+
| `additionalLines` | No | Array of `MathRow` objects | Second and subsequent top-level lines, in display order; omit when unused |
|
|
14
|
+
|
|
15
|
+
A document is always an object, not a bare node or array. All rows and nodes are embedded objects; IDs are addresses, not references that replace the objects.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
interface MathDocument {
|
|
19
|
+
version: 1;
|
|
20
|
+
root: MathRow;
|
|
21
|
+
additionalLines?: MathRow[];
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Minimal valid empty document:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"version": 1,
|
|
30
|
+
"root": {
|
|
31
|
+
"id": "row-root",
|
|
32
|
+
"children": [{ "type": "text", "id": "text-root", "text": "" }]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`root: null`, `children: []` and a missing `root` are invalid. `additionalLines: []` is accepted and omitted from the validated return value. `additionalLines: null` is invalid.
|
|
38
|
+
|
|
39
|
+
## 2. Rows and child ordering
|
|
40
|
+
|
|
41
|
+
| Field | Required | JSON type | Contract |
|
|
42
|
+
|---|---|---|---|
|
|
43
|
+
| `id` | Yes | String | Unique throughout this document, including both rows and nodes |
|
|
44
|
+
| `children` | Yes | Nonempty array of `MathNode` | Left-to-right formula content |
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
interface MathRow {
|
|
48
|
+
id: string;
|
|
49
|
+
children: MathNode[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type MathNode = MathText | MathStructure;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
A row has **no `type` field**. The root, each additional line and every structure slot all use this same row shape.
|
|
56
|
+
|
|
57
|
+
The first and last children must be text nodes. Every structure must have a text sibling immediately before and after it. Examples of child sequences:
|
|
58
|
+
|
|
59
|
+
| Sequence | Valid? | Reason |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| `text` | Yes | Empty or ordinary text-only row |
|
|
62
|
+
| `text → fraction → text` | Yes | Both outside caret boundaries exist |
|
|
63
|
+
| `text → root → text → superscript → text` | Yes | Text separates structures |
|
|
64
|
+
| `text → text` | Accepted | The validator permits adjacent text; generated edits normally merge them |
|
|
65
|
+
| `fraction → text` | No | Missing initial text boundary |
|
|
66
|
+
| `text → fraction` | No | Missing final text boundary |
|
|
67
|
+
| `text → fraction → root → text` | No | Missing text between structures |
|
|
68
|
+
|
|
69
|
+
Empty text nodes are meaningful **caret positions**, not padding. Do not delete them to reduce JSON size. `text: ""` differs from `text: " "`: the latter contains an actual space that exports as an escaped LaTeX space.
|
|
70
|
+
|
|
71
|
+
## 3. Text nodes
|
|
72
|
+
|
|
73
|
+
| Field | Required | JSON type | Contract |
|
|
74
|
+
|---|---|---|---|
|
|
75
|
+
| `type` | Yes | String | Exactly `"text"` |
|
|
76
|
+
| `id` | Yes | String | Unique document address |
|
|
77
|
+
| `text` | Yes | String | Actual characters, including empty text and Unicode symbol glyphs |
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
interface MathText {
|
|
81
|
+
type: 'text';
|
|
82
|
+
id: string;
|
|
83
|
+
text: string;
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
| Formula content | Stored text | Not stored |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| A variable | `"x"` | No variable declaration, inferred type or value |
|
|
90
|
+
| A number | `"123"` | No numeric JSON value; do not write `text: 123` |
|
|
91
|
+
| A symbol | `"α"`, `"≤"`, `"ℝ"` | No LaTeX command name or translation key |
|
|
92
|
+
| An ordinary expression | `"a+b"` | No implicit addition node |
|
|
93
|
+
| A visible brace | `"{"` | No invisible LaTeX grouping |
|
|
94
|
+
|
|
95
|
+
Variable/number/symbol colors are derived by tokenization at render time. They are not distinct JSON node types. Storing `"\\alpha"` in a text node means literal backslash text; use `parseLatex` when the source is LaTeX.
|
|
96
|
+
|
|
97
|
+
## 4. Structures and exact slot order
|
|
98
|
+
|
|
99
|
+
Ordinary structures require `type`, `id` and `slots`. Every slot is a complete `MathRow`, never a string, nullable value or node ID. Slot counts are exact, including empty slots.
|
|
100
|
+
|
|
101
|
+
| `type` | Exact slots | `slots[0]` | `slots[1]` | `slots[2]` |
|
|
102
|
+
|---|---:|---|---|---|
|
|
103
|
+
| `fraction` | 2 | Numerator | Denominator | — |
|
|
104
|
+
| `root` | 1 | Radicand | — | — |
|
|
105
|
+
| `vec`, `hat`, `overline` | 1 | Accented body | — | — |
|
|
106
|
+
| `quad`, `qquad` | 0 | — | — | — |
|
|
107
|
+
| `roman` | 1 | Upright math body | — | — |
|
|
108
|
+
| `limit` | 2 | Approach condition | Expression body | — |
|
|
109
|
+
| `binomial` | 2 | Upper term (n) | Lower term (k) | — |
|
|
110
|
+
| `indexedRoot` | 2 | Root index | Radicand | — |
|
|
111
|
+
| `textGroup` | 1 | Literal text row | — | — |
|
|
112
|
+
| `operatorName` | 1 | Literal operator name row | — | — |
|
|
113
|
+
| `superscript` | 2 | Base | Exponent | — |
|
|
114
|
+
| `subscript` | 2 | Base | Subscript | — |
|
|
115
|
+
| `scripts` | 3 | Shared base | Subscript | Superscript |
|
|
116
|
+
| `parentheses` | 1 | Enclosed content | — | — |
|
|
117
|
+
| `brackets` | 1 | Enclosed content | — | — |
|
|
118
|
+
| `absolute` | 1 | Enclosed content | — | — |
|
|
119
|
+
| `sum` | 3 | Lower limit | Upper limit | Body |
|
|
120
|
+
| `product` | 3 | Lower limit | Upper limit | Body |
|
|
121
|
+
| `integral` | 3 | Lower limit | Upper limit | Body |
|
|
122
|
+
|
|
123
|
+
The lower limit comes **before** the upper limit in JSON, regardless of visual position. `root` means square root. `indexedRoot` has exactly two slots: index first, then radicand. An empty index remains an editable slot; Backspace in that empty slot converts the node to `root` and preserves the radicand. `scripts` stores both scripts on one base; slot order is base, lower, upper. Tab follows that order. Empty script slots remain editable; Backspace in an empty script removes that side and converts the node to `superscript` or `subscript`.
|
|
124
|
+
|
|
125
|
+
The following type makes the runtime slot rules explicit. The source TypeScript API currently uses the looser `MathRow[]`; runtime validation enforces these counts.
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
type OrdinaryStructure =
|
|
129
|
+
| { type: 'quad' | 'qquad'; id: string; slots: [] }
|
|
130
|
+
| { type: 'braces' | 'angle' | 'openClosed' | 'closedOpen' | 'roman' | 'root' | 'parentheses' | 'brackets' | 'absolute' | 'vec' | 'hat' | 'overline' | 'textGroup' | 'operatorName';
|
|
131
|
+
id: string; slots: [MathRow] }
|
|
132
|
+
| { type: 'overset' | 'underset' | 'fraction' | 'limit' | 'binomial' | 'superscript' | 'subscript' | 'indexedRoot';
|
|
133
|
+
id: string; slots: [MathRow, MathRow] }
|
|
134
|
+
| { type: 'sum' | 'product' | 'integral' | 'doubleIntegral' | 'scripts';
|
|
135
|
+
id: string; slots: [MathRow, MathRow, MathRow] };
|
|
136
|
+
|
|
137
|
+
type MathStructure = OrdinaryStructure | MathMatrix | MathEquationGrid;
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Slots can contain nested structures using the same child ordering rules. A fraction denominator can therefore contain a square root without a new node format.
|
|
141
|
+
|
|
142
|
+
## 5. Grids: matrices, alignment and cases
|
|
143
|
+
|
|
144
|
+
| Field | Matrix | Aligned / cases |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| `type` | `"matrix"` | `"aligned"` or `"cases"` |
|
|
147
|
+
| `id` | Required unique string | Required unique string |
|
|
148
|
+
| `columns` | Required integer, 1–20 | Required integer, exactly `2` |
|
|
149
|
+
| `slots` | Required flat array of cell rows | Required flat array of cell rows |
|
|
150
|
+
| `environment` | Required allowed string below | Not part of these node types |
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
type MatrixEnvironment =
|
|
154
|
+
| 'matrix' | 'pmatrix' | 'bmatrix'
|
|
155
|
+
| 'Bmatrix' | 'vmatrix' | 'Vmatrix';
|
|
156
|
+
|
|
157
|
+
interface MathMatrix {
|
|
158
|
+
type: 'matrix';
|
|
159
|
+
id: string;
|
|
160
|
+
columns: number;
|
|
161
|
+
environment: MatrixEnvironment;
|
|
162
|
+
slots: MathRow[];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
interface MathEquationGrid {
|
|
166
|
+
type: 'aligned' | 'cases';
|
|
167
|
+
id: string;
|
|
168
|
+
columns: 2;
|
|
169
|
+
slots: MathRow[];
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
| Environment | Matrix delimiters |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `matrix` | None |
|
|
176
|
+
| `pmatrix` | Parentheses |
|
|
177
|
+
| `bmatrix` | Square brackets |
|
|
178
|
+
| `Bmatrix` | Braces |
|
|
179
|
+
| `vmatrix` | Single vertical bars |
|
|
180
|
+
| `Vmatrix` | Double vertical bars |
|
|
181
|
+
|
|
182
|
+
Rows are not stored as another matrix array. For zero-based cell coordinates `(r, c)`, the slot index is `r * columns + c`. The row count is `slots.length / columns`. A valid grid has 1–20 rows; slot count must be a positive multiple of `columns`.
|
|
183
|
+
|
|
184
|
+
For `aligned`, left/right cells are arbitrary formula rows: an equals sign must be stored explicitly in the appropriate text. For `cases`, each pair is value then condition. The node does not evaluate the condition.
|
|
185
|
+
|
|
186
|
+
Complete 2×2 identity-matrix document:
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"version": 1,
|
|
191
|
+
"root": {
|
|
192
|
+
"id": "r0",
|
|
193
|
+
"children": [
|
|
194
|
+
{ "type": "text", "id": "before", "text": "" },
|
|
195
|
+
{
|
|
196
|
+
"type": "matrix",
|
|
197
|
+
"id": "matrix0",
|
|
198
|
+
"columns": 2,
|
|
199
|
+
"environment": "bmatrix",
|
|
200
|
+
"slots": [
|
|
201
|
+
{ "id": "r00", "children": [{ "type": "text", "id": "t00", "text": "1" }] },
|
|
202
|
+
{ "id": "r01", "children": [{ "type": "text", "id": "t01", "text": "0" }] },
|
|
203
|
+
{ "id": "r10", "children": [{ "type": "text", "id": "t10", "text": "0" }] },
|
|
204
|
+
{ "id": "r11", "children": [{ "type": "text", "id": "t11", "text": "1" }] }
|
|
205
|
+
]
|
|
206
|
+
},
|
|
207
|
+
{ "type": "text", "id": "after", "text": "" }
|
|
208
|
+
]
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Identity matrices, zero matrices, vectors and formula templates do not have template node types or template IDs in the saved model. They produce ordinary editable cells/structures.
|
|
214
|
+
|
|
215
|
+
## 6. Multiple lines versus nested rows
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"version": 1,
|
|
220
|
+
"root": {
|
|
221
|
+
"id": "line1",
|
|
222
|
+
"children": [{ "type": "text", "id": "text1", "text": "a=b" }]
|
|
223
|
+
},
|
|
224
|
+
"additionalLines": [
|
|
225
|
+
{
|
|
226
|
+
"id": "line2",
|
|
227
|
+
"children": [{ "type": "text", "id": "text2", "text": "c=d" }]
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
This has two top-level lines and exports as `gathered`. A two-row matrix inside `root` still has only one top-level line. Inline mode forbids `additionalLines` containing rows; it does not forbid a matrix or cases node in the root. There is no `line`, `gathered` or line-number JSON node.
|
|
234
|
+
|
|
235
|
+
## 7. IDs, state and persistence
|
|
236
|
+
|
|
237
|
+
- IDs must be unique across all row and node objects within a document. They are opaque strings, not paths, source offsets or content hashes.
|
|
238
|
+
- Use library constructors/transformations to create IDs. Preserve IDs when saving and reopening the same tree; separate imports and pasted fragments receive fresh IDs.
|
|
239
|
+
- The current validator checks string type and uniqueness, but does not enforce UUID syntax or nonempty IDs. Producers should generate nonempty IDs and must not depend on a particular format.
|
|
240
|
+
- `MathDocument` does not contain selection, focus, undo history, locale, toolbar settings, pixel sizes or the original LaTeX source.
|
|
241
|
+
|
|
242
|
+
| Object | Purpose | Part of persisted `MathDocument`? |
|
|
243
|
+
|---|---|---|
|
|
244
|
+
| `MathCaret` | Text node `id`, UTF-16 `start`/`end`, optional affinity | No |
|
|
245
|
+
| `MathState` | `{ document, caret }` while editing | No; save its `document` |
|
|
246
|
+
| `MathRange` | Structural selection endpoints | No |
|
|
247
|
+
| `MathHistory` | Past/present/future editing states | No |
|
|
248
|
+
| `MathFragment` | Clipboard `{ version: 1, rows: MathRow[] }` | No; different envelope from a document |
|
|
249
|
+
| Host `tex` / `mathDocument` attributes | Product-specific storage container | No; the host may store serialized document JSON alongside derived LaTeX |
|
|
250
|
+
|
|
251
|
+
The model version is independent of npm versioning. Version 1 has no automatic migration from unknown future versions. Reject unsupported versions rather than guessing their structure.
|
|
252
|
+
|
|
253
|
+
## 8. Validation and limits
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
import { parseMathDocument, toLatex } from '@barocss/math-editor/core';
|
|
257
|
+
|
|
258
|
+
const document = parseMathDocument(savedJson);
|
|
259
|
+
if (document) {
|
|
260
|
+
session.load(document); // Deliberately resets session history.
|
|
261
|
+
const latex = toLatex(document);
|
|
262
|
+
} else {
|
|
263
|
+
// Keep existing content and report invalid saved data.
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
`parseMathDocument` returns a validated document or `undefined`; it does not return a diagnostic list. `parseLatex` has its own success/error result. Neither function mutates an editor.
|
|
268
|
+
|
|
269
|
+
| Check | Current bound / rule |
|
|
270
|
+
|---|---|
|
|
271
|
+
| Serialized input | At most 200,000 UTF-16 code units; not a byte limit |
|
|
272
|
+
| Structural validation envelope | The derived clipboard-shaped JSON must also fit the 200,000-unit bound |
|
|
273
|
+
| Top-level rows | 1–200, counting root |
|
|
274
|
+
| Nesting | Root row depth is 0; each descent into a structure slot adds 1; at most 40 |
|
|
275
|
+
| Object count | At most 10,000 row + node objects, including empty text boundaries |
|
|
276
|
+
| Grid dimensions | 1–20 rows and columns; aligned/cases require 2 columns |
|
|
277
|
+
| Node types and arity | Only listed types and exact slot counts |
|
|
278
|
+
| IDs | String type and uniqueness across the document |
|
|
279
|
+
| Text | String type; empty strings allowed |
|
|
280
|
+
|
|
281
|
+
The validator is **not a general metadata sanitizer**. Unknown properties are currently tolerated: the returned document envelope keeps only `version`, `root` and nonempty `additionalLines`, while nested row/node extras can remain. Extra properties have no defined semantics or preservation guarantee. Store application metadata outside this tree; do not use them to extend model behavior.
|
|
282
|
+
|
|
283
|
+
## 9. Invalid forms to avoid
|
|
284
|
+
|
|
285
|
+
| Invalid form | Correct representation |
|
|
286
|
+
|---|---|
|
|
287
|
+
| `root: [ ... ]` | `root: { id, children: [...] }` |
|
|
288
|
+
| `slots: ["a", "b"]` | Two embedded row objects, each with text children |
|
|
289
|
+
| `type: "variable"` or `type: "number"` | `type: "text"`, with a string value |
|
|
290
|
+
| `rows: [[a, b], [c, d]]` on a matrix | Flat `slots` plus `columns` |
|
|
291
|
+
| One missing sum limit | Keep the slot and give it an empty text row |
|
|
292
|
+
| Reused row ID as a text node ID | Generate different IDs for both objects |
|
|
293
|
+
| A structure without surrounding text | Add empty text boundaries |
|
|
294
|
+
| `version: "1"` | Numeric `version: 1` |
|
|
295
|
+
|
|
296
|
+
Use the [LaTeX mapping](LATEX-MODEL.md) for complete fraction and nested-expression examples, and [support scope](SUPPORT.md) for the corresponding editor features.
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
## Literal text and operator-name nodes (workspace)
|
|
300
|
+
|
|
301
|
+
Both use the ordinary `{ type, id, slots }` structure shape with exactly one slot. Every child of that slot must be a `MathText`; nested structures are invalid. `textGroup` preserves text such as `"if x > 0"` and exports `\text{if x > 0}`. `operatorName` stores the name, for example `"sin"`, and exports `\sin `; a custom name exports `\operatorname{custom}`. Neither node stores an argument or evaluates a function. The following argument is a sibling in the containing math row. A superscript can wrap the entire operator-name node.
|
|
302
|
+
|
|
303
|
+
Literal slots remain one editable input in React, including whitespace and punctuation. They do not trigger math suggestions. Escaping is applied during LaTeX export, not stored in `MathText.text`.
|
|
304
|
+
|
|
305
|
+
Accents store one complete math row in `slots[0]`. The mark is determined by the structure type and is never stored as a combining character in the body text. Unwrapping preserves that row. Slanted relation glyphs `⩾` and `⩽` remain ordinary symbol text.
|
|
306
|
+
|
|
307
|
+
### Explicit spacing, roman style and limit placement (workspace)
|
|
308
|
+
|
|
309
|
+
`quad` and `qquad` are zero-slot structures: `{ type: "quad", id: "…", slots: [] }`. They represent one and two em of math spacing. They still have surrounding text caret nodes, and can be copied, deleted and undone as model content. They are distinct from ordinary whitespace and empty caret hit areas.
|
|
310
|
+
|
|
311
|
+
`roman` has one editable math row and exports `\mathrm{…}`. Unlike `textGroup`, its content remains mathematical, with nested structures and mathematical whitespace rules. A parsed `\rm` declaration becomes a `roman` group that ends at the containing group or cell boundary.
|
|
312
|
+
|
|
313
|
+
The limit family (`limit`, `limsup`, `liminf`), sum/product and all supported integral kinds may carry optional boolean `limits`. Omitted means default placement, `true` preserves `\limits`, and `false` preserves `\nolimits`. Other values or node kinds are rejected. The directive is emitted immediately after the operator command. Existing `true` data remains valid; older builds reject the new `false` value.
|
|
314
|
+
|
|
315
|
+
## Additional fences and annotations (workspace)
|
|
316
|
+
|
|
317
|
+
| Input | Model | Editing |
|
|
318
|
+
| --- | --- | --- |
|
|
319
|
+
| `\left\{x\right\}` | `braces`, one body slot | Search `braces` or `중괄호` |
|
|
320
|
+
| `\langle u,v\rangle` or `\left\langle u,v\right\rangle` | `angle`, one body slot | Search `angle`, `내적` or `꺾쇠괄호` |
|
|
321
|
+
| `\left(a,b\right]` | `openClosed`, one body slot | Search `openclosed` or `열린닫힌구간` |
|
|
322
|
+
| `\left[a,b\right)` | `closedOpen`, one body slot | Search `closedopen` or `닫힌열린구간` |
|
|
323
|
+
| Other `\left…\right…` pairs, including `\left.x\right|` | `fenced`, one body slot and independent `left`/`right` | Imported ends are preserved; `.` denotes an invisible end |
|
|
324
|
+
| `\overset{a}{b}` | `overset`, `[annotation, body]` | Search `overset` or `위주석`; Tab moves from annotation to body |
|
|
325
|
+
| `\underset{a}{b}` | `underset`, `[annotation, body]` | Search `underset` or `아래주석`; Tab follows model order, even though the annotation is below |
|
|
326
|
+
|
|
327
|
+
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.
|
|
328
|
+
|
|
329
|
+
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` remain unsupported; brace annotations are implemented in the workspace.
|
|
330
|
+
|
|
331
|
+
The generic fence shape is `{ type: "fenced", id, slots: [body], left, right }`. `left` and `right` each use the physical-end set above. If supplied, both must be valid and may only appear on a `fenced` node. If omitted together, the pair defaults to `(` and `)`. The named preset nodes do not carry this metadata. Annotation slot order follows LaTeX argument order, not the visual top-to-bottom order of `underset`.
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
### Double integrals (workspace)
|
|
335
|
+
|
|
336
|
+
`\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 now implemented in the workspace; see the section below.
|
|
337
|
+
|
|
338
|
+
### Norm node (workspace)
|
|
339
|
+
|
|
340
|
+
| Field / input | Contract |
|
|
341
|
+
| --- | --- |
|
|
342
|
+
| `type` | `"norm"` |
|
|
343
|
+
| `id` | Unique structure ID |
|
|
344
|
+
| `slots` | Exactly one MathRow: `[body]`; nested math and empty content allowed |
|
|
345
|
+
| LaTeX aliases | `\lVert…\rVert`, `\left\|…\right\|`, `\left\Vert…\right\Vert` |
|
|
346
|
+
| Canonical export | `\left\Vert …\right\Vert ` |
|
|
347
|
+
| Generic fence metadata | `"‖"` is also a valid independent `left` / `right` value for `fenced` |
|
|
348
|
+
|
|
349
|
+
The document version remains 1. Older package builds do not understand this workspace node; validate saved documents against the consuming package's supported kinds.
|
|
350
|
+
|
|
351
|
+
## Triple and contour integrals (workspace)
|
|
352
|
+
|
|
353
|
+
| Input | Model | Slots / behavior |
|
|
354
|
+
| --- | --- | --- |
|
|
355
|
+
| `\iiint_a^b{f}` | `tripleIntegral` | `[lower, upper, body]` |
|
|
356
|
+
| `\oint_C{f}` | `contourIntegral` | `[lower, upper, body]`; absent upper stays empty |
|
|
357
|
+
|
|
358
|
+
Search `iiint` / `삼중적분` / `∭`, or `oint` / `폐곡선적분` / `∮`. Tab visits lower, upper and body. Backspace after the structure unwraps its contents; Undo restores it. Both accept and preserve `\limits` for stacked bounds; default editing places bounds alongside the slanted glyph. Unbraced input consumes one body atom, as for existing integrals. `\nolimits` is supported in the current workspace. These are editable notation, not integration or path analysis. Contour glyphs use the bundled KaTeX Size2-Regular U+222E directly, with its native ring and slant. No pseudo-element strokes are added.
|
|
359
|
+
|
|
360
|
+
## Brace annotations (workspace)
|
|
361
|
+
|
|
362
|
+
| Input | Model | Slots |
|
|
363
|
+
| --- | --- | --- |
|
|
364
|
+
| `\overbrace{a+b}^{n}` | `overbrace` | `[annotation, body]` |
|
|
365
|
+
| `\underbrace{a+b}_{n}` | `underbrace` | `[annotation, body]` |
|
|
366
|
+
|
|
367
|
+
Type `overbrace` / `위중괄호` or `underbrace` / `아래중괄호`. The annotation is edited first; Tab moves into the body. Wrapping a selection preserves it in the body and focuses the empty annotation. Both slots allow nested math. The brace stretches across the body/annotation container. Backspace immediately after the structure unwraps its contents; Undo restores the structure.
|
|
368
|
+
|
|
369
|
+
The parser accepts a bare brace body and creates an empty annotation. Export always includes the corresponding `^{…}` or `_{…}`, including an empty annotation. Repeated annotation markers fail. An opposite-side script is handled as an outer ordinary script, not a second brace annotation. Slot order is annotation then body even for underbraces; this matches overset/underset. This workspace addition retains document version 1; older consumers must reject unknown structure kinds during validation.
|
|
370
|
+
|
|
371
|
+
## Additional accents (workspace)
|
|
372
|
+
|
|
373
|
+
| Syntax | Model / appearance | Search |
|
|
374
|
+
| --- | --- | --- |
|
|
375
|
+
| `\tilde{x}` | `tilde`, fixed-width centered wave | `tilde`, `물결악센트` |
|
|
376
|
+
| `\bar{x}` | `bar`, short centered line | `bar`, `짧은윗줄` |
|
|
377
|
+
| `\dot{x}` | `dot`, one centered dot | `dot`, `위점` |
|
|
378
|
+
| `\ddot{x}` | `ddot`, two dots | `ddot`, `두점` |
|
|
379
|
+
| `\widehat{AB}` | `widehat`, body-width hat | `widehat`, `넓은모자` |
|
|
380
|
+
| `\widetilde{AB}` | `widetilde`, body-width wave | `widetilde`, `넓은물결` |
|
|
381
|
+
|
|
382
|
+
Each is a structure with a unique `id` and exactly one MathRow in `slots: [body]`. Empty and nested bodies are supported, including scripts and fractions. Import preserves the command spelling; export emits the same command with a braced body. Selection wrapping retains the original expression. Tab continues outside; Backspace after the wrapper preserves its body, and Undo restores it. `dot` can also match a multiplication symbol: choose Dot accent explicitly or search `위점`. `bar` remains distinct from the existing body-width `overline`. These additions are in 0.2.0 and do not evaluate derivatives or estimates.
|
|
383
|
+
|
|
384
|
+
## Limit variants and placement (workspace)
|
|
385
|
+
|
|
386
|
+
`\limsup_{n}{x}` and `\liminf_{n}{x}` use `limsup` / `liminf` structures with exactly `[condition, body]` rows. Search `limsup` / `상극한` or `liminf` / `하극한`; Tab moves from condition to body. They do not evaluate sequence limits.
|
|
387
|
+
|
|
388
|
+
All limit-family, sum/product and integral nodes accept a single `\limits` or `\nolimits` immediately after the command, before scripts. The optional `limits` field is now boolean: omitted means default, `true` is stacked placement, `false` is side placement. Both explicit values survive JSON validation, copying and LaTeX export. Repeated/conflicting directives are rejected. Existing `limits: true` remains valid; older package builds do not accept `false` or the new limit kinds.
|
|
389
|
+
|
|
390
|
+
Placement is currently selected through imported LaTeX/model metadata, not a dedicated toolbar switch. Imported side conditions remain editable; normal limit suggestions use default placement. This is 0.2.0 functionality.
|
|
391
|
+
|
|
392
|
+
## Fine mathematical spacing (workspace)
|
|
393
|
+
|
|
394
|
+
| LaTeX | Node type | Width | Suggestion search |
|
|
395
|
+
| --- | --- | --- | --- |
|
|
396
|
+
| `\,` | `thinSpace` | 3mu / 1⁄6em | `thinspace`, `얇은간격` |
|
|
397
|
+
| `\:` | `mediumSpace` | 4mu / 2⁄9em | `mediumspace`, `중간간격` |
|
|
398
|
+
| `\;` | `thickSpace` | 5mu / 5⁄18em | `thickspace`, `두꺼운간격` |
|
|
399
|
+
| `\!` | `negativeThinSpace` | −3mu / −1⁄6em | `negativethinspace`, `간격줄이기` |
|
|
400
|
+
|
|
401
|
+
All four nodes have a unique ID and `slots: []`, like quad/qquad. Import/export preserve the exact command; ordinary source whitespace is still normalized. Insertion resumes immediately after the spacer. Backspace removes it and Undo restores it. Negative spacing uses a zero-width node with negative inline-end margin, not an overlay that intercepts pointer input. Click the adjoining text to edit; the spacer has no text slot. Plain punctuation remains literal and is not a spacing trigger.
|
|
402
|
+
|
|
403
|
+
Editor dimensions scale in script/limit slots. These are explicit gaps added to the editor's own token clearance, not a complete TeX math-glue or script-style suppression engine. Arbitrary dimensions (`\hspace`, `\kern`) and rubber-glue stretch/shrink remain unsupported. This is 0.2.0 functionality.
|
|
404
|
+
|
|
405
|
+
## Explicit fraction and binomial sizes — workspace
|
|
406
|
+
|
|
407
|
+
| LaTeX | JSON `type` | `mathStyle` | Slots |
|
|
408
|
+
| --- | --- | --- | --- |
|
|
409
|
+
| `\frac{a}{b}` | `fraction` | Omitted | Numerator, denominator |
|
|
410
|
+
| `\dfrac{a}{b}` | `fraction` | `"display"` | Numerator, denominator |
|
|
411
|
+
| `\tfrac{a}{b}` | `fraction` | `"text"` | Numerator, denominator |
|
|
412
|
+
| `\binom{n}{k}` | `binomial` | Omitted | Upper term, lower term |
|
|
413
|
+
| `\dbinom{n}{k}` | `binomial` | `"display"` | Upper term, lower term |
|
|
414
|
+
| `\tbinom{n}{k}` | `binomial` | `"text"` | Upper term, lower term |
|
|
415
|
+
|
|
416
|
+
The parser, validated JSON, copy/paste and exporter preserve this optional field. It is valid only on `fraction` and `binomial`; other values or node kinds are rejected. Omission retains the ordinary structure behavior. Nested explicit sizes override the enclosing explicit size.
|
|
417
|
+
|
|
418
|
+
Both editing surfaces use larger display operands (22 px) and smaller text operands (16 px), retaining editable scripts and slots. This is a bounded editing presentation, not a complete TeX style cascade; `\displaystyle`, `\textstyle` and script-style declarations remain unsupported. KaTeX controls the final exported formula's typesetting.
|
|
419
|
+
|
|
420
|
+
## Mathematical alphabets — 0.2.0
|
|
421
|
+
|
|
422
|
+
| Input | Model kind | Editable slots | Intended glyph coverage |
|
|
423
|
+
| --- | --- | --- | --- |
|
|
424
|
+
| `\mathbf{Ax+2}` | `bold` | One math body | Bold upright Latin letters and numerals |
|
|
425
|
+
| `\mathcal{ABC}` | `calligraphic` | One math body | Calligraphic Latin capitals |
|
|
426
|
+
| `\mathbb{ABH}` | `blackboard` | One math body | Double-struck Latin capitals |
|
|
427
|
+
|
|
428
|
+
Type `mathbf`, `mathcal` or `mathbb` and select the suggestion, or import the LaTeX command. These structures can also wrap a selected expression. Their bodies remain math rows: fractions, scripts and nested font groups remain editable. Export preserves the scoped command. Existing single `\mathbb{N}`, `Z`, `Q`, `R`, `C` imports retain the previous Unicode-symbol normalization; multi-letter inputs use a `blackboard` structure.
|
|
429
|
+
|
|
430
|
+
The editor bundles KaTeX Main Bold, Caligraphic and AMS font faces with the existing MIT license. Decorative faces are limited to Latin capitals; other characters use fallback glyphs and are not claimed to match every KaTeX alphabet substitution. Semantic token colors remain visible. This does not add `\boldsymbol`, `\mathfrak`, arbitrary font declarations or mixed math inside literal `\text`.
|
|
431
|
+
|
|
432
|
+
## Labeled arrows — 0.2.0
|
|
433
|
+
|
|
434
|
+
| LaTeX | Model kind | `slots[0]` | `slots[1]` |
|
|
435
|
+
| --- | --- | --- | --- |
|
|
436
|
+
| `\xrightarrow[below]{above}` | `xrightarrow` | Upper label | Lower label |
|
|
437
|
+
| `\xleftarrow[below]{above}` | `xleftarrow` | Upper label | Lower label |
|
|
438
|
+
|
|
439
|
+
Both slots are math rows, including an empty lower row when the optional argument is absent. The exporter omits `[below]` when the lower label is empty, and always emits the upper `{above}` argument. Nested fractions and scripts stay editable. An unfinished optional bracket rejects the complete import without applying a partial document.
|
|
440
|
+
|
|
441
|
+
Type `xrightarrow`, `xleftarrow`, or search for “labeled arrow” / “설명화살표” in suggestions. Enter applies the candidate; edit the upper label, then Tab to the lower label. The shaft expands to fit the longer label while its arrowhead keeps a fixed size. Empty slots retain an editing affordance. This is a dedicated two-label arrow, distinct from ordinary arrow symbols and generic `overset` annotations. Other extensible arrow commands (`\xleftrightarrow`, harpoons, etc.) remain unsupported.
|