@drghaliasri/butex 3.2.2

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/README.md ADDED
@@ -0,0 +1,311 @@
1
+ # BuTeX
2
+
3
+ **Integrators:** treat [**Integration contract (host apps)**](#integration-contract-host-apps) as the source of truth for wiring MathJax and BuTeX. The **npm package** ships `dist/` and this `README.md` only; design notes and ADRs live in the GitHub repo under `docs/` (they are not included in the published tarball).
4
+
5
+ BuTeX is a browser-side foundation for Arabic mathematical typography and equation editing on top of [MathJax](https://www.mathjax.org/). It ships thin MathJax extensions such as `\arabsqrt` (Arabic-style mirrored radical; CommonHTML uses CSS mirror/unmirror, SVG uses the MathJax SVG pipeline plus BuTeX SVG helpers where applicable), optional GUI/document layers, and helpers such as `parseBuTeX`. The longer-term direction is a GUI editor driven by structured equation ASTs.
6
+
7
+ The vendored **[MathJax-src/](MathJax-src/)** folder in this repo is **reference only** — runtime integration uses the **`mathjax`** npm package.
8
+
9
+ ## Project direction
10
+
11
+ The intended editor model is **AST-first**:
12
+
13
+ - A GUI edits equation nodes rather than raw LaTeX strings.
14
+ - BuTeX will maintain local TypeScript ASTs for English and Arabic equation structures so users can switch views.
15
+ - A remote service may send equations as JSON compatible with the local ASTs; the browser imports that JSON and renders/edit it locally.
16
+ - Arabic/English TeX strings are generated from the AST for MathJax rendering. Remote-rendered strings can be useful for testing/debugging, but should not be the editor state.
17
+ - The Python files in **[references/](references/)** are reference material for the emerging JSON shape and conversion behavior.
18
+
19
+ ## Rendering outputs and MVP scope
20
+
21
+ - **CommonHTML (`chtml`)** and **SVG** are supported. Load the matching MathJax 4 bundle (`tex-chtml.js` vs `tex-svg.js`) and pass the same mode as `output` to `renderBuTeXMathIsland` / `mountBuTeXMathIsland`. `<ButexEditor />` and document math preview follow the loaded bundle when possible (`tex2chtml` vs `tex2svg`).
22
+ - `\arabsqrt`: optional index + mandatory radicand (same argument shape as `\sqrt`).
23
+ - Compatibility macros: `\arsqrt` aliases `\arabsqrt`; `\unit{...}` and `\idx{...}` are browser-side passthrough wrappers for imported/reference TeX.
24
+ - **Arabic surface parser** (`parseBuTeX`) — optional helper that maps a tiny subset of Arabic command names to MathJax-safe TeX before rendering (see below). Long-term editor state is structured AST/JSON, not this string transform alone.
25
+
26
+ ## Arabic preprocessor (`parseBuTeX`)
27
+
28
+ Convert Arabic-friendly math surface syntax to plain MathJax TeX:
29
+
30
+
31
+ | Input | Output |
32
+ | ------------------- | -------------------------------- |
33
+ | `\جذر[3]{س}` | `\arabsqrt[3]{\text{س}}` |
34
+ | `\كسر{1}{2}` | `\frac{1}{2}` |
35
+ | `\كسر{\جذر{س}}{10}` | `\frac{\arabsqrt{\text{س}}}{10}` |
36
+ | `\جتا` | `\arcos` |
37
+
38
+
39
+ Pure string transform — call `parseBuTeX(tex)`, pass the result to MathJax.
40
+
41
+ ```ts
42
+ import { parseBuTeX } from 'butex';
43
+
44
+ const mjTex = parseBuTeX(String.raw`\جذر[3]{س}`);
45
+ // await renderBuTeXMathIsland(mjTex, { display: true, output: 'chtml' }) ...
46
+ ```
47
+
48
+ Run tests with `npm test`. Live parser demo: `demo/parser.html` (after `npm run build`; serve the repo root).
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ npm install butex mathjax
54
+ ```
55
+
56
+ Peer dependency: `mathjax` ^4.x (aligned with MathJax 4 components). If you use **`butex/react`**, also install **`react`** and **`react-dom`** (^18 or ^19).
57
+
58
+ ## Usage (browser)
59
+
60
+ 1. Load MathJax (e.g. `tex-chtml.js` or `tex-svg.js`) **after** setting `window.MathJax` config.
61
+ 2. Load BuTeX’s IIFE bundle (`dist/index.global.js` exposes global `BuTeX`).
62
+ 3. In `MathJax.startup.ready`, call `BuTeX.registerBuTeX(MathJax)` before `MathJax.startup.defaultReady()`.
63
+ 4. Inject styles once: `BuTeX.injectBuTeXStyles()` (or embed `BuTeX.BUTEX_CHROME_CSS` yourself).
64
+ 5. Render math with `renderBuTeXMathIsland(tex, options?)` or mount into a host element via `mountBuTeXMathIsland(host, tex, options?)`. Pass `output: 'svg'` when the host loads `tex-svg.js`, or `output: 'chtml'` with `tex-chtml.js`. For raw Arabic TeX that did not come from the editor AST, pass `mirrorOperators: true` to wrap directional operators according to BuTeX's shared operator table. `ButexEditor` picks SVG vs CHTML automatically from the loaded MathJax bundle (`tex2svg` vs `tex2chtml`).
65
+
66
+ Ensure TeX `packages` includes `butex-arabic-math` (use `BUTEX_TEX_PACKAGE` in config when using `{ '[+]': [...] }`).
67
+
68
+ ## Usage (npm / bundler)
69
+
70
+ ### MathJax entrypoints (bundlers)
71
+
72
+ Exact import strings depend on your bundler and how it resolves the `mathjax` package. Typical ESM imports for MathJax 4 components:
73
+
74
+ | Desired output | Typical import |
75
+ | -------------- | -------------- |
76
+ | CommonHTML | `import MathJax from 'mathjax/tex-chtml.js'` |
77
+ | SVG | `import MathJax from 'mathjax/tex-svg.js'` |
78
+
79
+ Use the same mode in `renderBuTeXMathIsland` / `mountBuTeXMathIsland` via `output: 'chtml'` or `output: 'svg'`. If resolution fails, point your import at whatever path your build resolves to the same component bundle (see MathJax’s docs for your version).
80
+
81
+ ```ts
82
+ import MathJax from 'mathjax/tex-chtml.js'; // or tex-svg.js — match `output` in render calls
83
+ import {
84
+ registerBuTeX,
85
+ injectBuTeXStyles,
86
+ renderBuTeXMathIsland,
87
+ BUTEX_TEX_PACKAGE,
88
+ } from 'butex';
89
+
90
+ // Before startup resolves — same timing rules as browser:
91
+ MathJax.startup.ready = () => {
92
+ registerBuTeX(MathJax);
93
+ injectBuTeXStyles();
94
+ MathJax.startup.defaultReady();
95
+ };
96
+ MathJax.config.tex = {
97
+ packages: { '[+]': ['ams', BUTEX_TEX_PACKAGE] },
98
+ };
99
+ ```
100
+
101
+ Maintainer-facing design notes live in the GitHub repo under `docs/` (not shipped on npm).
102
+
103
+ ## Integration contract (host apps)
104
+
105
+ BuTeX supports two integration modes. Keep this contract for stable behavior.
106
+
107
+ ### 1) Render-only contract (MathJax + BuTeX macros)
108
+
109
+ Use this when you only need to render LaTeX strings:
110
+
111
+ - Register BuTeX with MathJax via `registerBuTeX(MathJax)`.
112
+ - Include `butex-arabic-math` in TeX packages.
113
+ - Inject BuTeX MathJax styles once via `injectBuTeXStyles(document)` (or embed `BUTEX_CHROME_CSS`).
114
+ - Render expressions with `renderBuTeXMathIsland` / `mountBuTeXMathIsland` (set `output` to match the host bundle: `tex-svg.js` vs `tex-chtml.js`).
115
+ - For raw Arabic TeX, set `mirrorOperators: true` or call `mirrorBuTeXOperatorsInTex(tex)` before non-island MathJax typesetting. Leave it off for editor-generated TeX because the editor already emits `\butexmirror{...}`.
116
+ - For Arabic-friendly surface strings, optionally preprocess with `parseBuTeX(...)` before rendering.
117
+
118
+ This mode does not require the GUI editor runtime.
119
+
120
+ ### 2) GUI editor contract (shippable editor UX)
121
+
122
+ Use this when you want the same editor UX as the demo:
123
+
124
+ - Inject editor styles once via `injectBuTeXEditorStyles(document)` (or embed `BUTEX_EDITOR_CSS`).
125
+ - Create runtime with `Editor.createEditorRuntime({ ... })`.
126
+ - Pass your editor surface element as `surfaceEl`.
127
+ - Optionally pass `buttonElements` (undo/redo/copy/cut/split toggle) for auto button state refresh.
128
+ - Wire your toolbar/actions to runtime methods (`toggleSide`, `insertDelimiterByKind`, `addSup`, `addSub`, `removeSup`, `removeSub`, `deleteStructure`, `performUndo`, `performRedo`, `performCopy`, `performCut`, `performPaste`).
129
+ - Use `onSessionChange(session)` to render external previews (e.g., MathJax pane, status labels).
130
+
131
+ Minimal browser example:
132
+
133
+ ```ts
134
+ BuTeX.injectBuTeXEditorStyles(document);
135
+ const runtime = BuTeX.Editor.createEditorRuntime({
136
+ surfaceEl: document.getElementById('surface'),
137
+ onSessionChange: (session) => {
138
+ // host-render math preview/status here
139
+ },
140
+ });
141
+ ```
142
+
143
+ ### React widget (`butex/react`)
144
+
145
+ Shipped as a separate entry so apps that do not use React never pull it in.
146
+
147
+ - Import: `import { ButexEditor } from 'butex/react'`.
148
+ - Peer dependencies when using this entry: `react` and `react-dom` (^18 or ^19).
149
+ - The component wraps `Editor.createEditorRuntime` (toolbar, surface, MathJax preview strip, optional dev panels via `debug`).
150
+ - Load MathJax and register BuTeX **before** relying on the preview (same timing as the GUI contract above). Equation preview in `ButexEditor` uses the same `renderBuTeXMathIsland` path as document math islands (output follows the loaded bundle: SVG when `tex2svg` is available, otherwise CHTML). Optional legacy `mountBuTeXMathTypeset` (`typesetPromise`) remains exported for hosts that still rely on it.
151
+ - Next.js App Router: put BuTeX in a **client component** (`'use client'`).
152
+
153
+ ```tsx
154
+ 'use client';
155
+
156
+ import { ButexEditor } from 'butex/react';
157
+
158
+ export default function Page() {
159
+ return <ButexEditor />;
160
+ }
161
+ ```
162
+
163
+ ### Document AST (`butex/document`)
164
+
165
+ Use this headless entry when a host app or remote service already has `DocumentObject` JSON and needs a structured import/export layer.
166
+
167
+ ```ts
168
+ import {
169
+ fromDocumentJson,
170
+ createEmptyDocument,
171
+ renderDocumentLatex,
172
+ buildDocumentPreview,
173
+ } from 'butex/document';
174
+
175
+ const documentNode = fromDocumentJson({
176
+ node_type: 'DocumentObject',
177
+ blocks: [{ command: '\\section', value: 'Intro $x$' }],
178
+ });
179
+
180
+ const emptyDocument = createEmptyDocument();
181
+ const latex = renderDocumentLatex(documentNode);
182
+ const preview = buildDocumentPreview(documentNode);
183
+ ```
184
+
185
+ V1 supports simple headings, paragraphs, `itemize` / `enumerate`, `tabular`, `includegraphics`, raw blocks, and math spans detected inside text. The document layer detects math delimiters and can align them with ordered imported `MathObject` JSON; it does not parse arbitrary equation LaTeX into chain nodes in the browser.
186
+
187
+ ### Document React widget (`butex/react-document`)
188
+
189
+ Use this entry for the first document-editor UI. It edits supported document blocks, shows a live semantic preview, and opens the equation editor for supported imported math islands.
190
+
191
+ ```tsx
192
+ 'use client';
193
+
194
+ import { ButexDocumentEditor } from 'butex/react-document';
195
+
196
+ export default function Page() {
197
+ return (
198
+ <ButexDocumentEditor
199
+ debug
200
+ onLatexChange={(latex) => console.log(latex)}
201
+ />
202
+ );
203
+ }
204
+ ```
205
+
206
+ The preview is semantic HTML for document structure. Math is emitted as escaped per-span islands, so host apps still need the normal MathJax + BuTeX registration when they want typeset math preview rather than TeX placeholders.
207
+
208
+ ### Document AST v2 (`butex/document2`)
209
+
210
+ Use this parallel v2 headless entry for the token-owned document model. Imported delimited math is converted into math tokens between prose tokens; normal editing should mutate text tokens and math tokens separately rather than treating raw delimited TeX as one textarea value.
211
+
212
+ ```ts
213
+ import {
214
+ fromDocumentJson2,
215
+ document2Latex,
216
+ document2Preview,
217
+ } from 'butex/document2';
218
+
219
+ const documentNode = fromDocumentJson2({
220
+ node_type: 'DocumentObject',
221
+ blocks: [{ command: '\\paragraph', value: 'نص $x$' }],
222
+ });
223
+
224
+ const latex = document2Latex(documentNode);
225
+ const preview = document2Preview(documentNode, 'svg');
226
+ ```
227
+
228
+ V2 exports Arabic TeX by default for equations saved from the embedded editor. Imported raw-only math is preserved as raw source and marked non-editable until a structured equation object is attached.
229
+
230
+ ### Document React widget v2 (`butex/react-document2`)
231
+
232
+ Use this entry for the new document editor integration. It opens embedded `<ButexEditor />` sessions in Arabic/RTL mode by default and renders document preview math islands with MathJax **SVG** by default (`mathOutput` defaults to `'svg'`; pass `mathOutput="chtml"` only if the host loads `tex-chtml.js`).
233
+
234
+ ```tsx
235
+ 'use client';
236
+
237
+ import { ButexDocumentEditor2 } from 'butex/react-document2';
238
+
239
+ export default function Page() {
240
+ return (
241
+ <ButexDocumentEditor2
242
+ debug
243
+ onLatexChange={(latex) => console.log(latex)}
244
+ />
245
+ );
246
+ }
247
+ ```
248
+
249
+ Host apps still register BuTeX with MathJax before preview rendering. Document editor v2 defaults to **`tex-svg.js`**; use `tex-chtml.js` only if you pass `mathOutput="chtml"`.
250
+
251
+ ### Styling/theming contract
252
+
253
+ - Core editor classes (`.surface`, `.chain`, `.slot`, `.node`, `.scripts`, `.delim*`, etc.) are shipped from `src/editor/styles.ts`.
254
+ - Theme with **one namespace**, `--butex-*`, on `.butex-widget` (or an ancestor of the editor surface). Defaults are set on `ButexEditor`’s root; hosts override accents, borders, **`--butex-surface-bg` / `--butex-surface-fg`**, **`--butex-preview-bg` / `--butex-preview-fg`**, focus/slot/selection, scripts, optional **`--butex-dev-*`** / **`--butex-dev-inset-*`** when using `debug`, etc., without redefining structure classes.
255
+ - Document editor theming is scoped under `.butex-document-widget`. Override `--butex-document-*` variables on that root or an ancestor, especially `--butex-document-bg`, `--butex-document-fg`, `--butex-document-panel`, `--butex-document-border`, `--butex-document-accent`, `--butex-document-preview-bg`, `--butex-document-drawer-bg`, `--butex-document-input-bg`, `--butex-document-table-border`, `--butex-document-math-bg`, and `--butex-document-dev-bg`.
256
+ - Document editor v2 theming is scoped under `.butex-document2-widget`. Override `--butex-document2-*` variables on that root or an ancestor; defaults inherit from the existing `--butex-*` variables where practical, including accent, panel, border, preview, focus, danger/error, and debug colors. The embedded equation editor opens as a centered modal (backdrop + panel); its BuTeX chrome reads the same `--butex-document2-*` tokens via a scoped bridge, so hosts normally theme once on `.butex-document2-widget` without a second equation theme. The built-in editor uses a **sticky compact icon toolbar** (heading glyphs, inline `$…$` vs display `\[…\]` math buttons with tooltips, table size popover), **focus-aware block insertion** after the active block, **↑/↓ block reorder**, **math delete** (chip × and drawer button), **document undo/redo** (toolbar + Ctrl/⌘Z, Ctrl/⌘Shift+Z, Ctrl+Y) via AST snapshots, and theme-tinted block cards. Preview tables use fixed black cell borders; raw `\raw` blocks stay out of preview until dedicated rendering exists.
257
+
258
+ ## Demo
259
+
260
+ From repo root (after `npm run build`):
261
+
262
+ ```bash
263
+ npm run demo
264
+ ```
265
+
266
+ Open:
267
+
268
+ - `http://localhost:4173/demo/` (MathJax + `\arabsqrt`)
269
+ - `http://localhost:4173/demo/parser.html` (parser-only normalize preview)
270
+ - **Equation editor (React):** run `cd demo/editor-app && npm install && npm run dev`, then open the URL Vite prints (see `demo/editor.html` for a short pointer).
271
+ - **Document editor (React):** run `cd demo/document-editor-app && npm install && npm run dev`, then open the URL Vite prints (see `demo/document.html` for a short pointer).
272
+ - **Document editor v2 (React):** run `cd demo/document-editor-app2 && npm install && npm run dev`, then open the URL Vite prints. The v2 demo loads MathJax SVG and includes grouped insert controls (sections, lists, tables, figures, equations), per-block delete, list item controls, and editor/preview toggles. Optional bottom dev panels (LaTeX + AST) appear only if you append `?debug=1` to the URL or pass the `debug` prop from your host app.
273
+
274
+ ### Editor MVP notes (`demo/editor-app`)
275
+
276
+ - Editing is AST-first (not raw LaTeX text editing).
277
+ - End-user UI is Arabic-first.
278
+ - Supported structures in this MVP: chars, numbers, operators, delimiter pairs `()`, `[]`, `{}`, fractions (`\frac`), and sup/sub chains.
279
+ - Caret: **Left/Right** walk every insertion gap in a fixed depth-first order (baseline and nested chains). **Up/Down** move between superscript and subscript where applicable (from the baseline, Up prefers superscript and Down prefers subscript when both exist). On the Arabic surface, arrow keys follow RTL progression.
280
+ - Char/number typing: by default keystrokes **merge into the same leaf** along the caret gap (digits only extend `number`, letters only extend `char`). Use the **Split leaf typing** toolbar control to toggle **split mode** (one new leaf per keystroke). **Delete/Backspace** trim inside the merged string when split mode is off.
281
+ - Structural edits are mirrored across Arabic/English trees; text edits apply to the active side.
282
+ - **Undo/redo:** full-session snapshots (`createUndoRedoStacks`, `pushUndoRedoSnapshot`, `restoreUndo`, `restoreRedo`). The demo exposes toolbar buttons plus **Ctrl+Z** / **⌘Z** for undo and **Ctrl+Shift+Z** / **⌘⇧Z** / **Ctrl+Y** for redo while the editing surface is focused. Caret moves and language switching are not recorded so undo targets content edits only. History caps at ~100 steps by default (`DEFAULT_UNDO_HISTORY_MAX_DEPTH`).
283
+ - **Selection + copy/cut/paste:** range selection lives at the slot level inside a single chain (no cross-chain selection in the MVP). Whole nodes are the copy unit, so superscripts/subscripts/inner subtrees always travel with their owner.
284
+ - **Keyboard:** **Shift+ArrowLeft/ArrowRight** extend selection (RTL-aware), **Ctrl/⌘+C/X/V** copy/cut/paste, **Backspace/Delete** and typing **replace** an active selection.
285
+ - **Mouse:** click-drag from one slot to another inside the same chain. Cross-chain mouse moves are ignored.
286
+ - **Clipboard format:** `application/x-butex-fragment+json` with both EN and AR mirrored nodes (`EditorFragment` v1) plus a **plain-text LaTeX fallback** for cross-app pasting. External plain text re-enters through the typing path so split/merge mode applies.
287
+ - **Future node types:** the single helper `nodeChildChains(node)` enumerates a node's nested chains. Adding command, env, or math-object kinds (see `[references/nodes.py](references/nodes.py)`) only requires extending this helper; selection, copy, paste, and id-remap stay unchanged.
288
+ - Debug panels are dev-only (`debug` prop on `<ButexEditor />`, or `?debug=1` in the Vite demo URL).
289
+ - Includes LaTeX dump, passive chain preview, and copyable command/render log.
290
+
291
+ ### Editor theming (CSS variables)
292
+
293
+ Override these on `:root` (or a host container) to customize colors:
294
+
295
+ - `--butex-bg`
296
+ - `--butex-fg`
297
+ - `--butex-muted`
298
+ - `--butex-accent`
299
+ - `--butex-border`
300
+ - `--butex-panel`
301
+ - `--butex-caret`
302
+ - `--butex-selected`
303
+ - `--butex-error`
304
+ - `--butex-error-bg`
305
+ - `--butex-accent-hover` (optional, demo toolbar)
306
+ - `--butex-shadow-sm` / `--butex-shadow-md` (optional, panels)
307
+ - `--butex-dev-bg` / `--butex-dev-border` / `--butex-dev-badge` / `--butex-dev-muted` (optional, developer-only strips when `debug` is on)
308
+
309
+ ## License
310
+
311
+ ISC (BuTeX package). MathJax is Apache-2.0 — see upstream.
@@ -0,0 +1,307 @@
1
+ type ArabicNodeJson = {
2
+ node_type: string;
3
+ expr?: string;
4
+ name?: string;
5
+ superscript?: ChainJson | null;
6
+ subscript?: ChainJson | null;
7
+ left_delim_expr?: string;
8
+ right_delim_expr?: string;
9
+ inner_expr?: ChainJson;
10
+ optional_args?: ChainJson[];
11
+ mandatory_args?: ChainJson[];
12
+ opening?: string;
13
+ closing?: string;
14
+ lines?: ChainJson[];
15
+ };
16
+ type ChainJson = {
17
+ node_type: 'ChainClass';
18
+ chain: ArabicNodeJson[];
19
+ };
20
+ declare class State {
21
+ }
22
+ declare class BaseAstNode {
23
+ state: State;
24
+ superscript: ChainNode | null;
25
+ subscript: ChainNode | null;
26
+ nodeType: string;
27
+ constructor(state?: State | null);
28
+ latex(): string;
29
+ arabicLatex(): string;
30
+ toArabicLatex(): string;
31
+ /** Latin-style scripts: `^{...}` and `_{...}` for english_json rendering. */
32
+ latexScripts(): string;
33
+ /**
34
+ * Arabic scripts use \\prescript{sup}{sub}{content} (matches reference Python output).
35
+ * Call with rendered strings for sup/sub chains (may be empty strings).
36
+ */
37
+ arabicLatexScripts(sup: string, sub: string, content: string): string;
38
+ }
39
+ declare class ChainNode {
40
+ chain: BaseAstNode[];
41
+ constructor(chain?: BaseAstNode[]);
42
+ latex(): string;
43
+ toEnglishLatex(): string;
44
+ /** RTL: reverse order relative to Latin chain (see test/ref_tests.txt). */
45
+ arabicLatex(): string;
46
+ toArabicLatex(): string;
47
+ }
48
+
49
+ type DocumentParseMode = 'english' | 'arabic';
50
+ type MathObjectJson = {
51
+ node_type: 'MathObject';
52
+ math_mode: string;
53
+ lines: ChainJson[];
54
+ closing: string;
55
+ };
56
+ type DocumentJson = {
57
+ node_type: 'DocumentObject';
58
+ blocks: DocumentBlockJson[];
59
+ };
60
+ type DocumentBlockJson = {
61
+ command: string;
62
+ value?: string;
63
+ math_objects?: MathObjectJson[];
64
+ options?: Record<string, string>;
65
+ closing?: string;
66
+ items?: DocumentListItemJson[];
67
+ rows?: string[][];
68
+ columns?: string;
69
+ };
70
+ type DocumentListItemJson = {
71
+ value: string;
72
+ math_objects?: MathObjectJson[];
73
+ blocks?: DocumentBlockJson[];
74
+ };
75
+ type DocumentCommand = '\\section' | '\\subsection' | '\\subsubsection' | '\\paragraph' | '\\begin{itemize}' | '\\begin{enumerate}' | '\\begin{tabular}' | '\\includegraphics' | '\\raw';
76
+ type MathNode = {
77
+ nodeType: 'MathObject';
78
+ mathMode: string;
79
+ lines: ChainNode[];
80
+ closing: string;
81
+ };
82
+ type TextInline = {
83
+ kind: 'text';
84
+ text: string;
85
+ };
86
+ type MathInline = {
87
+ kind: 'math';
88
+ source: string;
89
+ math: MathNode | null;
90
+ };
91
+ type DocumentInline = TextInline | MathInline;
92
+ type TextBlockNode = {
93
+ kind: 'textBlock';
94
+ command: '\\section' | '\\subsection' | '\\subsubsection' | '\\paragraph';
95
+ value: string;
96
+ inlines: DocumentInline[];
97
+ };
98
+ type ListItemNode = {
99
+ value: string;
100
+ inlines: DocumentInline[];
101
+ blocks: DocumentBlockNode[];
102
+ };
103
+ type ListBlockNode = {
104
+ kind: 'list';
105
+ command: '\\begin{itemize}' | '\\begin{enumerate}';
106
+ closing: '\\end{itemize}' | '\\end{enumerate}';
107
+ items: ListItemNode[];
108
+ };
109
+ type TableBlockNode = {
110
+ kind: 'table';
111
+ command: '\\begin{tabular}';
112
+ closing: '\\end{tabular}';
113
+ columns: string;
114
+ rows: DocumentInline[][][];
115
+ };
116
+ type ImageBlockNode = {
117
+ kind: 'image';
118
+ command: '\\includegraphics';
119
+ value: string;
120
+ options: Record<string, string>;
121
+ };
122
+ type RawBlockNode = {
123
+ kind: 'raw';
124
+ command: '\\raw';
125
+ value: string;
126
+ };
127
+ type DocumentBlockNode = TextBlockNode | ListBlockNode | TableBlockNode | ImageBlockNode | RawBlockNode;
128
+ type DocumentNode = {
129
+ nodeType: 'DocumentObject';
130
+ blocks: DocumentBlockNode[];
131
+ };
132
+ type DetectedMathSpan = {
133
+ start: number;
134
+ end: number;
135
+ source: string;
136
+ mathMode: string;
137
+ closing: string;
138
+ };
139
+ type DocumentPreviewMathIsland = {
140
+ id: string;
141
+ tex: string;
142
+ display: boolean;
143
+ };
144
+ type DocumentPreview = {
145
+ html: string;
146
+ mathIslands: DocumentPreviewMathIsland[];
147
+ };
148
+
149
+ declare function fromDocumentJson(json: unknown, mode?: DocumentParseMode): DocumentNode;
150
+ declare function createEmptyDocument(): DocumentNode;
151
+ declare function createTextBlock(command: TextBlockNode['command'], value?: string): TextBlockNode;
152
+ declare function createRawBlock(value?: string): RawBlockNode;
153
+ declare function createListBlock(command: ListBlockNode['command'], items?: string[]): ListBlockNode;
154
+ declare function createTableBlock(rows?: string[][], columns?: string): TableBlockNode;
155
+ declare function createImageBlock(value?: string, options?: Record<string, string>): ImageBlockNode;
156
+ declare function isSupportedDocumentCommand(command: string): command is DocumentCommand;
157
+
158
+ declare function fromMathObjectJson(json: unknown, mode?: DocumentParseMode): MathNode;
159
+ declare function renderMathNodeLatex(math: MathNode): string;
160
+ declare function isDisplayMathNode(math: MathNode): boolean;
161
+
162
+ type EditorSide = 'english' | 'arabic';
163
+ type EditorNodeKind = 'char' | 'number' | 'operator' | 'delimiter' | 'frac' | 'sqrt' | 'env' | 'atomicCommand' | 'atomicOperatorCommand';
164
+ type MatrixEnvStyle = 'matrix' | 'pmatrix' | 'bmatrix' | 'Bmatrix' | 'vmatrix' | 'Vmatrix';
165
+ type GridEnvName = MatrixEnvStyle | 'array' | 'aligned';
166
+ type EnvColumnAlignment = 'l' | 'c' | 'r';
167
+ type CharacterFontId = 'default' | 'takween' | 'diwani' | 'diwaniOutline';
168
+ type DigitFormId = 'western' | 'arabicIndic' | 'persianIndic';
169
+ type EditorSyncState = 'synced' | 'diverged';
170
+ type EditorChain = {
171
+ id: string;
172
+ nodes: EditorNode[];
173
+ };
174
+ type EditorNode = {
175
+ id: string;
176
+ kind: EditorNodeKind;
177
+ expr: string;
178
+ characterFont?: CharacterFontId;
179
+ superscript: EditorChain | null;
180
+ subscript: EditorChain | null;
181
+ leftDelimExpr: string;
182
+ rightDelimExpr: string;
183
+ innerExpr: EditorChain | null;
184
+ numerator: EditorChain | null;
185
+ denominator: EditorChain | null;
186
+ radicand: EditorChain | null;
187
+ rootIndex: EditorChain | null;
188
+ envName: GridEnvName | null;
189
+ matrixStyle: MatrixEnvStyle | null;
190
+ matrixRows: EditorChain[][] | null;
191
+ columnAlignments: EnvColumnAlignment[] | null;
192
+ };
193
+ type EditorCaret = {
194
+ chainId: string;
195
+ index: number;
196
+ };
197
+ type EditorSelectionRange = {
198
+ chainId: string;
199
+ anchorIndex: number;
200
+ focusIndex: number;
201
+ };
202
+ type EditorSyncMap = Record<string, {
203
+ expr: EditorSyncState;
204
+ }>;
205
+ type EditorDebugEntry = {
206
+ timestamp: string;
207
+ command: string;
208
+ activeSide: EditorSide;
209
+ englishLatex: string;
210
+ arabicLatex: string;
211
+ renderOk: boolean;
212
+ renderError: string | null;
213
+ note: string;
214
+ };
215
+ type EditorSession = {
216
+ englishTree: EditorChain;
217
+ arabicTree: EditorChain;
218
+ activeSide: EditorSide;
219
+ /** When true, each typed letter/digit inserts a new char/number leaf; when false, merge into adjacent same-kind leaves. */
220
+ splitLeafTyping: boolean;
221
+ /** One-shot: when true, the next insertChar/insertNumber bypasses leaf-merge (used by Space in merge mode). */
222
+ forceSplitNextLeaf: boolean;
223
+ /** When true and active side is Arabic, merged number typing prepends digits (RTL-style); still mirrored on EN/AR. */
224
+ arabicReverseNumberTyping: boolean;
225
+ /** Font applied to newly typed char nodes. */
226
+ currentCharacterFont: CharacterFontId;
227
+ /** Digit glyph form used for editor display and LaTeX preview output. */
228
+ currentDigitForm: DigitFormId;
229
+ caret: EditorCaret;
230
+ /** Whole-node range selection inside a single chain (anchor==focus means no selection). */
231
+ selection: EditorSelectionRange | null;
232
+ selectedNodeId: string | null;
233
+ selectedStructureNodeId: string | null;
234
+ sync: EditorSyncMap;
235
+ debugLog: EditorDebugEntry[];
236
+ };
237
+
238
+ type MathNodeFromEditorResult = {
239
+ ok: true;
240
+ math: MathNode;
241
+ } | {
242
+ ok: false;
243
+ reason: string;
244
+ };
245
+ declare function mathNodeFromEditorSession(session: EditorSession, mathMode?: string, closing?: string): MathNodeFromEditorResult;
246
+ /** Delimited Arabic TeX for document text/preview — matches `<ButexEditor />` arabic output. */
247
+ declare function mathSourceFromEditorSession(session: EditorSession, mathMode?: string, closing?: string): string;
248
+
249
+ declare function detectMathSpans(value: string): DetectedMathSpan[];
250
+ declare function buildInlines(value: string, mathObjects?: MathObjectJson[], mode?: DocumentParseMode): DocumentInline[];
251
+
252
+ declare function renderDocumentLatex(document: DocumentNode): string;
253
+ declare function buildDocumentPreview(document: DocumentNode): DocumentPreview;
254
+
255
+ type NewDocumentBlockKind = 'section' | 'subsection' | 'subsubsection' | 'paragraph' | 'itemize' | 'enumerate' | 'table' | 'image' | 'raw';
256
+ type DocumentMathTarget = {
257
+ scope: 'block';
258
+ blockIndex: number;
259
+ spanIndex: number;
260
+ } | {
261
+ scope: 'listItem';
262
+ blockIndex: number;
263
+ itemIndex: number;
264
+ spanIndex: number;
265
+ } | {
266
+ scope: 'tableCell';
267
+ blockIndex: number;
268
+ rowIndex: number;
269
+ columnIndex: number;
270
+ spanIndex: number;
271
+ };
272
+ type DocumentInlineTarget = {
273
+ scope: 'block';
274
+ blockIndex: number;
275
+ } | {
276
+ scope: 'listItem';
277
+ blockIndex: number;
278
+ itemIndex: number;
279
+ } | {
280
+ scope: 'tableCell';
281
+ blockIndex: number;
282
+ rowIndex: number;
283
+ columnIndex: number;
284
+ };
285
+ declare function createDocumentBlock(kind: NewDocumentBlockKind): DocumentBlockNode;
286
+ declare function addDocumentBlock(document: DocumentNode, kind: NewDocumentBlockKind): DocumentNode;
287
+ declare function removeDocumentBlock(document: DocumentNode, blockIndex: number): DocumentNode;
288
+ declare function moveDocumentBlock(document: DocumentNode, blockIndex: number, direction: -1 | 1): DocumentNode;
289
+ declare function updateTextBlockValue(document: DocumentNode, blockIndex: number, value: string): DocumentNode;
290
+ declare function updateRawBlockValue(document: DocumentNode, blockIndex: number, value: string): DocumentNode;
291
+ declare function updateImageBlock(document: DocumentNode, blockIndex: number, value: string, options: Record<string, string>): DocumentNode;
292
+ declare function updateListItemValue(document: DocumentNode, blockIndex: number, itemIndex: number, value: string): DocumentNode;
293
+ declare function addListItem(document: DocumentNode, blockIndex: number): DocumentNode;
294
+ declare function removeListItem(document: DocumentNode, blockIndex: number, itemIndex: number): DocumentNode;
295
+ declare function addNestedListBlock(document: DocumentNode, blockIndex: number, itemIndex: number, kind: 'itemize' | 'enumerate'): DocumentNode;
296
+ declare function updateTableCell(document: DocumentNode, blockIndex: number, rowIndex: number, columnIndex: number, value: string): DocumentNode;
297
+ declare function updateTableColumns(document: DocumentNode, blockIndex: number, columns: string): DocumentNode;
298
+ declare function addTableRow(document: DocumentNode, blockIndex: number): DocumentNode;
299
+ declare function removeTableRow(document: DocumentNode, blockIndex: number): DocumentNode;
300
+ declare function addTableColumn(document: DocumentNode, blockIndex: number): DocumentNode;
301
+ declare function removeTableColumn(document: DocumentNode, blockIndex: number): DocumentNode;
302
+ declare function replaceMathSpanSource(document: DocumentNode, target: DocumentMathTarget, source: string): DocumentNode;
303
+ declare function replaceMathSpanFromEditor(document: DocumentNode, target: DocumentMathTarget, math: MathNode, source?: string): DocumentNode;
304
+ declare function insertMathSpanFromEditor(document: DocumentNode, target: DocumentInlineTarget, insertAt: number, math: MathNode, source?: string): DocumentNode;
305
+ declare function documentInlineText(inlines: DocumentInline[]): string;
306
+
307
+ export { type DetectedMathSpan, type DocumentBlockJson, type DocumentBlockNode, type DocumentCommand, type DocumentInline, type DocumentInlineTarget, type DocumentJson, type DocumentListItemJson, type DocumentMathTarget, type DocumentNode, type DocumentParseMode, type DocumentPreview, type DocumentPreviewMathIsland, type ImageBlockNode, type ListBlockNode, type ListItemNode, type MathInline, type MathNode, type MathObjectJson, type NewDocumentBlockKind, type RawBlockNode, type TableBlockNode, type TextBlockNode, type TextInline, addDocumentBlock, addListItem, addNestedListBlock, addTableColumn, addTableRow, buildDocumentPreview, buildInlines, createDocumentBlock, createEmptyDocument, createImageBlock, createListBlock, createRawBlock, createTableBlock, createTextBlock, detectMathSpans, documentInlineText, fromDocumentJson, fromMathObjectJson, insertMathSpanFromEditor, isDisplayMathNode, isSupportedDocumentCommand, mathNodeFromEditorSession, mathSourceFromEditorSession, moveDocumentBlock, removeDocumentBlock, removeListItem, removeTableColumn, removeTableRow, renderDocumentLatex, renderMathNodeLatex, replaceMathSpanFromEditor, replaceMathSpanSource, updateImageBlock, updateListItemValue, updateRawBlockValue, updateTableCell, updateTableColumns, updateTextBlockValue };