@yurikilian/lex4 0.1.1 → 0.2.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 (79) hide show
  1. package/README.md +243 -27
  2. package/dist/ast/block-mapper.d.ts +24 -0
  3. package/dist/ast/block-mapper.d.ts.map +1 -0
  4. package/dist/ast/content-mapper.d.ts +16 -0
  5. package/dist/ast/content-mapper.d.ts.map +1 -0
  6. package/dist/ast/document-serializer.d.ts +11 -0
  7. package/dist/ast/document-serializer.d.ts.map +1 -0
  8. package/dist/ast/index.d.ts +9 -0
  9. package/dist/ast/index.d.ts.map +1 -0
  10. package/dist/ast/inline-mapper.d.ts +41 -0
  11. package/dist/ast/inline-mapper.d.ts.map +1 -0
  12. package/dist/ast/payload-builder.d.ts +18 -0
  13. package/dist/ast/payload-builder.d.ts.map +1 -0
  14. package/dist/ast/types.d.ts +112 -0
  15. package/dist/ast/types.d.ts.map +1 -0
  16. package/dist/components/EditorSidebar.d.ts +1 -0
  17. package/dist/components/EditorSidebar.d.ts.map +1 -1
  18. package/dist/components/HistorySidebar.d.ts.map +1 -1
  19. package/dist/components/Lex4Editor.d.ts +9 -1
  20. package/dist/components/Lex4Editor.d.ts.map +1 -1
  21. package/dist/components/PageBody.d.ts +1 -2
  22. package/dist/components/PageBody.d.ts.map +1 -1
  23. package/dist/components/PageFooter.d.ts.map +1 -1
  24. package/dist/components/PageHeader.d.ts.map +1 -1
  25. package/dist/components/Toolbar.d.ts.map +1 -1
  26. package/dist/components/VariablePanel.d.ts +15 -0
  27. package/dist/components/VariablePanel.d.ts.map +1 -0
  28. package/dist/components/VariablePicker.d.ts +14 -0
  29. package/dist/components/VariablePicker.d.ts.map +1 -0
  30. package/dist/extensions/ast-extension.d.ts +16 -0
  31. package/dist/extensions/ast-extension.d.ts.map +1 -0
  32. package/dist/extensions/extension-context.d.ts +29 -0
  33. package/dist/extensions/extension-context.d.ts.map +1 -0
  34. package/dist/extensions/index.d.ts +6 -0
  35. package/dist/extensions/index.d.ts.map +1 -0
  36. package/dist/extensions/types.d.ts +74 -0
  37. package/dist/extensions/types.d.ts.map +1 -0
  38. package/dist/extensions/variables-extension.d.ts +31 -0
  39. package/dist/extensions/variables-extension.d.ts.map +1 -0
  40. package/dist/i18n/context.d.ts +28 -0
  41. package/dist/i18n/context.d.ts.map +1 -0
  42. package/dist/i18n/defaults.d.ts +3 -0
  43. package/dist/i18n/defaults.d.ts.map +1 -0
  44. package/dist/i18n/index.d.ts +4 -0
  45. package/dist/i18n/index.d.ts.map +1 -0
  46. package/dist/i18n/types.d.ts +82 -0
  47. package/dist/i18n/types.d.ts.map +1 -0
  48. package/dist/index.d.ts +10 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/lex4-editor.cjs +1113 -101
  51. package/dist/lex4-editor.cjs.map +1 -1
  52. package/dist/lex4-editor.js +1116 -104
  53. package/dist/lex4-editor.js.map +1 -1
  54. package/dist/lexical/editor-setup.d.ts +5 -1
  55. package/dist/lexical/editor-setup.d.ts.map +1 -1
  56. package/dist/lexical/plugins/font-plugin.d.ts +1 -1
  57. package/dist/lexical/plugins/font-plugin.d.ts.map +1 -1
  58. package/dist/lexical/plugins/font-size-plugin.d.ts +19 -0
  59. package/dist/lexical/plugins/font-size-plugin.d.ts.map +1 -0
  60. package/dist/lexical/plugins/index.d.ts +2 -0
  61. package/dist/lexical/plugins/index.d.ts.map +1 -1
  62. package/dist/style.css +532 -160
  63. package/dist/types/editor-handle.d.ts +14 -0
  64. package/dist/types/editor-handle.d.ts.map +1 -0
  65. package/dist/types/editor-props.d.ts +23 -0
  66. package/dist/types/editor-props.d.ts.map +1 -1
  67. package/dist/variables/index.d.ts +5 -0
  68. package/dist/variables/index.d.ts.map +1 -0
  69. package/dist/variables/types.d.ts +26 -0
  70. package/dist/variables/types.d.ts.map +1 -0
  71. package/dist/variables/variable-commands.d.ts +11 -0
  72. package/dist/variables/variable-commands.d.ts.map +1 -0
  73. package/dist/variables/variable-context.d.ts +21 -0
  74. package/dist/variables/variable-context.d.ts.map +1 -0
  75. package/dist/variables/variable-node.d.ts +34 -0
  76. package/dist/variables/variable-node.d.ts.map +1 -0
  77. package/dist/variables/variable-plugin.d.ts +8 -0
  78. package/dist/variables/variable-plugin.d.ts.map +1 -0
  79. package/package.json +1 -1
package/README.md CHANGED
@@ -22,19 +22,23 @@ A paginated document editor built as a **reusable React library** on top of [Met
22
22
 
23
23
  <div align="center">
24
24
 
25
- ![Editor with formatted content](docs/screenshots/editor-with-content.png)
25
+ ![Editor with formatted content](https://raw.githubusercontent.com/yurikilian/lex4/main/docs/screenshots/editor-with-content.png)
26
26
 
27
27
  </div>
28
28
 
29
29
  ## ✨ Features
30
30
 
31
- - **True A4 pagination** — every page is exactly 794 × 1123 CSS pixels (210mm × 297mm at 96 DPI)
31
+ - **True A4 pagination** — every page is exactly 794 × 1123 CSS pixels (210 mm × 297 mm at 96 DPI)
32
32
  - **Automatic content flow** — overflow splits at block boundaries, underflow pulls content back
33
33
  - **Rich text formatting** — bold, italic, underline, strikethrough, alignment, lists, indentation
34
34
  - **Headers & footers** — global toggle with per-page editable regions and page counters
35
- - **Multiple font families** — Arial, Times New Roman, Courier New, Georgia, Verdana and more
35
+ - **Multiple font families** — Inter, Arial, Times New Roman, Courier New, Georgia, Verdana and more
36
+ - **Font size control** — per-selection font size with AST-level preservation
36
37
  - **Session history sidebar** — Word-style action timeline with full undo/redo
37
- - **Serializable document model** — typed AST export/import for backend persistence
38
+ - **Extension architecture** — opt-in features via composable extensions (`astExtension`, `variablesExtension`)
39
+ - **Document AST export** — clean, versioned, Lexical-independent AST for backend DOCX/PDF rendering
40
+ - **Variables & placeholders** — insert dynamic tokens like `{{customer.name}}` with metadata export
41
+ - **i18n support** — all UI strings externalized; override any subset for localization
38
42
  - **Read-only mode** — disable editing while keeping the document viewable
39
43
  - **Zero config** — drop in the component and start editing
40
44
 
@@ -43,28 +47,28 @@ A paginated document editor built as a **reusable React library** on top of [Met
43
47
  <details>
44
48
  <summary><strong>Empty Editor</strong> — clean A4 page ready for editing</summary>
45
49
 
46
- ![Empty editor](docs/screenshots/editor-empty.png)
50
+ ![Empty editor](https://raw.githubusercontent.com/yurikilian/lex4/main/docs/screenshots/editor-empty.png)
47
51
 
48
52
  </details>
49
53
 
50
54
  <details>
51
55
  <summary><strong>Headers & Footers</strong> — global toggle with editable regions</summary>
52
56
 
53
- ![Editor with headers and footers](docs/screenshots/editor-header-footer.png)
57
+ ![Editor with headers and footers](https://raw.githubusercontent.com/yurikilian/lex4/main/docs/screenshots/editor-header-footer.png)
54
58
 
55
59
  </details>
56
60
 
57
61
  <details>
58
62
  <summary><strong>Multi-Page Document</strong> — automatic content flow across pages</summary>
59
63
 
60
- ![Multi-page document](docs/screenshots/editor-multi-page.png)
64
+ ![Multi-page document](https://raw.githubusercontent.com/yurikilian/lex4/main/docs/screenshots/editor-multi-page.png)
61
65
 
62
66
  </details>
63
67
 
64
68
  <details>
65
69
  <summary><strong>Toolbar</strong> — full formatting controls</summary>
66
70
 
67
- ![Toolbar](docs/screenshots/toolbar.png)
71
+ ![Toolbar](https://raw.githubusercontent.com/yurikilian/lex4/main/docs/screenshots/toolbar.png)
68
72
 
69
73
  </details>
70
74
 
@@ -80,10 +84,10 @@ yarn add @yurikilian/lex4
80
84
 
81
85
  ### Peer Dependencies
82
86
 
83
- The library requires React 18+ and Lexical 0.22+ as peer dependencies:
87
+ The library requires React 18+ as a peer dependency. Lexical packages are bundled.
84
88
 
85
89
  ```bash
86
- npm install react react-dom lexical @lexical/react @lexical/rich-text @lexical/list @lexical/history @lexical/selection @lexical/utils @lexical/clipboard @lexical/html
90
+ npm install react react-dom
87
91
  ```
88
92
 
89
93
  ## 🚀 Quick Start
@@ -101,22 +105,48 @@ function App() {
101
105
  }
102
106
  ```
103
107
 
104
- ### With Initial Document
108
+ ### With Extensions
109
+
110
+ Extensions add opt-in capabilities. The two built-in extensions are `astExtension` (document AST export) and `variablesExtension` (dynamic variable placeholders):
105
111
 
106
112
  ```tsx
107
- import { Lex4Editor, createEmptyDocument } from '@yurikilian/lex4';
113
+ import { useRef, useMemo } from 'react';
114
+ import {
115
+ Lex4Editor,
116
+ Lex4EditorHandle,
117
+ astExtension,
118
+ variablesExtension,
119
+ VariableDefinition,
120
+ } from '@yurikilian/lex4';
108
121
  import '@yurikilian/lex4/style.css';
109
122
 
123
+ const variables: VariableDefinition[] = [
124
+ { key: 'customer.name', label: 'Customer Name', group: 'Customer', valueType: 'string' },
125
+ { key: 'proposal.date', label: 'Proposal Date', group: 'Proposal', valueType: 'date' },
126
+ ];
127
+
110
128
  function App() {
111
- const initialDoc = createEmptyDocument();
129
+ const editorRef = useRef<Lex4EditorHandle>(null);
130
+
131
+ const extensions = useMemo(() => [
132
+ astExtension(),
133
+ variablesExtension(variables),
134
+ ], []);
135
+
136
+ const handleSave = () => {
137
+ const ast = editorRef.current?.getDocumentAst();
138
+ console.log(JSON.stringify(ast, null, 2));
139
+ };
112
140
 
113
141
  return (
114
- <Lex4Editor
115
- initialDocument={initialDoc}
116
- headerFooterEnabled={true}
117
- onDocumentChange={(doc) => saveToBackend(doc)}
118
- onHeaderFooterToggle={(enabled) => console.log('Headers:', enabled)}
119
- />
142
+ <>
143
+ <Lex4Editor
144
+ ref={editorRef}
145
+ extensions={extensions}
146
+ onDocumentChange={(doc) => console.log(doc)}
147
+ />
148
+ <button onClick={handleSave}>Export AST</button>
149
+ </>
120
150
  );
121
151
  }
122
152
  ```
@@ -143,9 +173,57 @@ The main editor component. Drop it into any React application.
143
173
  | `headerFooterEnabled` | `boolean` | `false` | Initial header/footer toggle state |
144
174
  | `onHeaderFooterToggle` | `(enabled: boolean) => void` | — | Called when the user toggles headers/footers |
145
175
  | `readOnly` | `boolean` | `false` | Disable editing (view-only mode) |
176
+ | `extensions` | `Lex4Extension[]` | `[]` | Extensions to load (e.g., `astExtension()`, `variablesExtension(defs)`) |
177
+ | `translations` | `DeepPartial<Lex4Translations>` | English | Partial i18n overrides, deep-merged with defaults |
178
+ | `onSave` | `(payload: { ast, json }) => void` | — | Called when the host app triggers a save |
146
179
  | `captureHistoryShortcutsOnWindow` | `boolean` | `true` | Capture ⌘Z/⌘⇧Z at the window level |
147
180
  | `className` | `string` | — | Additional CSS class for the editor root |
148
181
 
182
+ ### Extensions
183
+
184
+ Extensions are opt-in feature modules that add capabilities to the editor without coupling.
185
+
186
+ #### `astExtension()`
187
+
188
+ Adds document AST export. Contributes imperative handle methods:
189
+
190
+ | Method | Signature | Description |
191
+ |--------|-----------|-------------|
192
+ | `getDocumentAst()` | `() => DocumentAst` | Returns the document as a clean, typed AST |
193
+ | `getDocumentJson()` | `() => string` | Returns the AST serialized as formatted JSON |
194
+ | `buildSavePayload(opts?)` | `(opts?) => SaveDocumentRequest` | Wraps the AST into a REST-ready payload |
195
+
196
+ ```tsx
197
+ const extensions = [astExtension()];
198
+ // then via ref:
199
+ const ast = editorRef.current?.getDocumentAst();
200
+ ```
201
+
202
+ #### `variablesExtension(definitions)`
203
+
204
+ Adds variable placeholders — dynamic tokens rendered as non-editable chips in the editor and preserved as structured nodes in the exported AST.
205
+
206
+ | Method | Signature | Description |
207
+ |--------|-----------|-------------|
208
+ | `insertVariable(key)` | `(key: string) => void` | Inserts a variable at the current cursor position |
209
+ | `refreshVariables(defs)` | `(defs: VariableDefinition[]) => void` | Updates the available variable definitions |
210
+
211
+ Also adds:
212
+ - **Toolbar button** — variable picker dropdown for inserting variables inline
213
+ - **Side panel toggle** — opens a searchable variable panel on the right
214
+ - **Variable node** — custom Lexical node rendered as a non-editable chip
215
+
216
+ ```tsx
217
+ const variables: VariableDefinition[] = [
218
+ { key: 'customer.name', label: 'Customer Name', group: 'Customer', valueType: 'string' },
219
+ { key: 'proposal.date', label: 'Proposal Date', group: 'Proposal', valueType: 'date' },
220
+ ];
221
+
222
+ const extensions = [variablesExtension(variables)];
223
+ ```
224
+
225
+ In the exported AST, variables appear as `{ type: "variable", key: "customer.name" }` nodes within block content, and their definitions appear under `metadata.variables`.
226
+
149
227
  ### Types
150
228
 
151
229
  ```ts
@@ -206,6 +284,123 @@ These hooks are exported for advanced use cases where you need to build custom p
206
284
  | `useOverflowDetection` | Monitors content height and triggers reflow when content exceeds the page body |
207
285
  | `useHeaderFooter` | Header/footer state management and chrome template application |
208
286
 
287
+ ## 🌐 i18n (Internationalization)
288
+
289
+ All 59 UI strings are externalized and can be overridden via the `translations` prop. No external i18n library is forced on consumers.
290
+
291
+ ### Basic Override
292
+
293
+ ```tsx
294
+ <Lex4Editor
295
+ translations={{
296
+ toolbar: { undo: 'Desfazer', redo: 'Refazer', bold: 'Negrito (Ctrl+B)' },
297
+ header: { placeholder: 'Cabeçalho' },
298
+ footer: { placeholder: 'Rodapé' },
299
+ }}
300
+ />
301
+ ```
302
+
303
+ ### Bridge with i18next
304
+
305
+ If your app already uses `i18next`, bridge it:
306
+
307
+ ```tsx
308
+ import { useTranslation } from 'react-i18next';
309
+
310
+ function App() {
311
+ const { t } = useTranslation();
312
+
313
+ return (
314
+ <Lex4Editor
315
+ translations={{
316
+ toolbar: {
317
+ undo: t('editor.undo'),
318
+ redo: t('editor.redo'),
319
+ bold: t('editor.bold'),
320
+ },
321
+ }}
322
+ />
323
+ );
324
+ }
325
+ ```
326
+
327
+ ### Available String Keys
328
+
329
+ | Section | Keys | Examples |
330
+ |---------|------|---------|
331
+ | `toolbar` | 16 | `undo`, `redo`, `bold`, `italic`, `alignLeft`, `numberedList`, ... |
332
+ | `history` | 4 + 20 actions | `title`, `empty`, `actions.boldApplied`, `actions.fontChanged`, ... |
333
+ | `variables` | 8 | `title`, `available`, `searchPlaceholder`, `openPanel`, ... |
334
+ | `header` / `footer` | 1 each | `placeholder` |
335
+ | `sidebar` | 1 | `close` |
336
+
337
+ Dynamic strings use `{{key}}` interpolation: `"Font changed to {{value}}"`.
338
+
339
+ Import `DEFAULT_TRANSLATIONS` and `Lex4Translations` to see the full shape:
340
+
341
+ ```tsx
342
+ import { DEFAULT_TRANSLATIONS } from '@yurikilian/lex4';
343
+ import type { Lex4Translations } from '@yurikilian/lex4';
344
+ ```
345
+
346
+ ## 📝 Document AST
347
+
348
+ The AST is a **clean, versioned, Lexical-independent** structure designed for backend consumption (e.g., DOCX/PDF generation). It preserves semantic structure, formatting marks, font choices, header/footer layout, A4 page metadata, and variable references.
349
+
350
+ > **Requires `astExtension()`** — the AST export is opt-in via the extension system.
351
+
352
+ ```ts
353
+ // Export via imperative ref
354
+ const ast = editorRef.current?.getDocumentAst();
355
+
356
+ // Or build a REST payload
357
+ const payload = editorRef.current?.buildSavePayload({
358
+ exportTarget: 'pdf',
359
+ documentId: 'doc-123',
360
+ });
361
+
362
+ await fetch('/api/documents/export', {
363
+ method: 'POST',
364
+ headers: { 'Content-Type': 'application/json' },
365
+ body: JSON.stringify(payload),
366
+ });
367
+ ```
368
+
369
+ ### AST Shape (top-level)
370
+
371
+ ```ts
372
+ interface DocumentAst {
373
+ version: '1.0.0';
374
+ page: {
375
+ format: 'A4';
376
+ widthMm: 210;
377
+ heightMm: 297;
378
+ margins: { topMm, rightMm, bottomMm, leftMm };
379
+ };
380
+ headerFooter: {
381
+ enabled: boolean;
382
+ pageCounterMode: 'none' | 'header' | 'footer' | 'both';
383
+ defaultHeader: ContentAst | null;
384
+ defaultFooter: ContentAst | null;
385
+ };
386
+ pages: PageAst[];
387
+ metadata: {
388
+ variables: Record<string, VariableDefinitionAst>;
389
+ };
390
+ }
391
+ ```
392
+
393
+ ### REST Payload
394
+
395
+ ```ts
396
+ interface SaveDocumentRequest {
397
+ document: DocumentAst;
398
+ exportTarget?: 'pdf' | 'docx';
399
+ documentId?: string;
400
+ metadata?: Record<string, string>;
401
+ }
402
+ ```
403
+
209
404
  ## 🏗️ Architecture
210
405
 
211
406
  ### Multi-Editor Discrete Page Model
@@ -214,7 +409,7 @@ Unlike most web-based "paginated" editors that use a single editor with CSS visu
214
409
 
215
410
  <div align="center">
216
411
 
217
- ![Component tree](docs/screenshots/arch-component-tree.png)
412
+ ![Component tree](https://raw.githubusercontent.com/yurikilian/lex4/main/docs/screenshots/arch-component-tree.png)
218
413
 
219
414
  </div>
220
415
 
@@ -224,10 +419,27 @@ The pagination engine is built as **pure functions** that transform page state a
224
419
 
225
420
  <div align="center">
226
421
 
227
- ![Content flow](docs/screenshots/arch-content-flow.png)
422
+ ![Content flow](https://raw.githubusercontent.com/yurikilian/lex4/main/docs/screenshots/arch-content-flow.png)
228
423
 
229
424
  </div>
230
425
 
426
+ ### Extension Architecture
427
+
428
+ Features are added via composable extensions that can contribute nodes, plugins, toolbar items, side panels, context providers, and imperative handle methods:
429
+
430
+ ```ts
431
+ interface Lex4Extension {
432
+ name: string;
433
+ nodes?: Klass<LexicalNode>[]; // custom Lexical nodes
434
+ bodyPlugins?: React.ComponentType[]; // plugins per page editor
435
+ toolbarItems?: React.ComponentType[]; // toolbar UI additions
436
+ sidePanel?: React.ComponentType; // right-side panel
437
+ provider?: React.ComponentType<...>; // context provider wrapper
438
+ themeOverrides?: Partial<EditorThemeClasses>;
439
+ handleMethods?: (ctx) => Record<string, Function>;
440
+ }
441
+ ```
442
+
231
443
  ### Key Invariants
232
444
 
233
445
  - Every page is **exactly** A4 (794 × 1123 px at 96 DPI) — no dynamic heights
@@ -241,21 +453,25 @@ The pagination engine is built as **pure functions** that transform page state a
241
453
  ```
242
454
  lex4/
243
455
  ├── packages/
244
- │ └── editor/ # lex4 — the publishable library
456
+ │ └── editor/ # @yurikilian/lex4 — the publishable library
245
457
  │ ├── src/
458
+ │ │ ├── ast/ # AST types, serializers, block/inline mappers, payload builder
246
459
  │ │ ├── components/ # React components (Lex4Editor, PageView, Toolbar, etc.)
247
460
  │ │ ├── constants/ # A4 dimensions, layout math
248
461
  │ │ ├── context/ # DocumentProvider, document reducer, actions
249
462
  │ │ ├── engine/ # Pagination logic — pure functions (reflow, overflow, paginate)
463
+ │ │ ├── extensions/ # Extension system, astExtension, variablesExtension
250
464
  │ │ ├── hooks/ # usePagination, useOverflowDetection, useHeaderFooter
465
+ │ │ ├── i18n/ # Translations types, defaults, context provider
251
466
  │ │ ├── lexical/ # Editor config, plugins (paste, history), custom commands
252
467
  │ │ ├── types/ # TypeScript interfaces (Lex4Document, PageState, etc.)
253
- │ │ └── utils/ # Editor state manipulation helpers
468
+ │ │ ├── utils/ # Editor state manipulation helpers
469
+ │ │ └── variables/ # VariableNode, VariablePlugin, VariableProvider
254
470
  │ └── dist/ # Built output (ESM + CJS + types + CSS)
255
471
  ├── demo/ # Demo app (deployed to GitHub Pages)
256
472
  ├── e2e/ # Playwright end-to-end tests
257
473
  ├── .github/workflows/ # CI, npm publish, GitHub Pages deployment
258
- └── docs/screenshots/ # Auto-generated screenshots for README
474
+ └── docs/screenshots/ # Screenshots for README
259
475
  ```
260
476
 
261
477
  ## 🛠️ Development
@@ -312,8 +528,8 @@ pnpm --filter e2e test:ui
312
528
 
313
529
  | Category | Framework | Count | Description |
314
530
  |----------|-----------|-------|-------------|
315
- | Unit | Vitest | 89 | Engine logic, reducers, utilities, component rendering |
316
- | E2E | Playwright | 80 | Full user flows — typing, formatting, pagination, header/footer, history |
531
+ | Unit | Vitest | 178 | Engine logic, reducers, AST serializers, i18n, variable nodes |
532
+ | E2E | Playwright | 118 | Full user flows — typing, formatting, pagination, header/footer, variables, theme, i18n |
317
533
 
318
534
  ## 🔧 Build & Bundle
319
535
 
@@ -327,7 +543,7 @@ The library is built with **Vite in library mode**, producing:
327
543
  | CSS | `dist/style.css` | Compiled Tailwind styles |
328
544
  | Source maps | `dist/*.map` | Debugging support |
329
545
 
330
- React, ReactDOM, and all `@lexical/*` packages are **externalized** — they are not bundled and must be provided by the consuming application.
546
+ React and ReactDOM are **externalized** — they are not bundled and must be provided by the consuming application. Lexical packages are bundled as direct dependencies.
331
547
 
332
548
  ## 🚢 Publishing to npm
333
549
 
@@ -0,0 +1,24 @@
1
+ import { BlockNodeAst } from './types';
2
+ /** Generic serialized Lexical element node shape. */
3
+ interface SerializedElementNode {
4
+ type: string;
5
+ children?: SerializedElementNode[];
6
+ format?: number | string;
7
+ indent?: number;
8
+ direction?: string;
9
+ tag?: string;
10
+ listType?: string;
11
+ start?: number;
12
+ value?: number;
13
+ [key: string]: unknown;
14
+ }
15
+ /**
16
+ * Maps a single serialized Lexical block node to an AST block node.
17
+ */
18
+ export declare function mapBlockNode(node: SerializedElementNode): BlockNodeAst;
19
+ /**
20
+ * Maps an array of serialized Lexical block nodes to AST block nodes.
21
+ */
22
+ export declare function mapBlockNodes(nodes: SerializedElementNode[]): BlockNodeAst[];
23
+ export {};
24
+ //# sourceMappingURL=block-mapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block-mapper.d.ts","sourceRoot":"","sources":["../../src/ast/block-mapper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACV,YAAY,EAQb,MAAM,SAAS,CAAC;AASjB,qDAAqD;AACrD,UAAU,qBAAqB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACnC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAmBD;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,qBAAqB,GAAG,YAAY,CAiBtE;AAuED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,qBAAqB,EAAE,GAAG,YAAY,EAAE,CAE5E"}
@@ -0,0 +1,16 @@
1
+ import { SerializedEditorState } from 'lexical';
2
+ import { BlockNodeAst, ContentAst } from './types';
3
+ /**
4
+ * Converts a Lexical SerializedEditorState into a ContentAst.
5
+ *
6
+ * Returns null if the state is null/undefined (e.g. disabled header).
7
+ */
8
+ export declare function mapEditorStateToContent(state: SerializedEditorState | null | undefined): ContentAst | null;
9
+ /**
10
+ * Converts a Lexical SerializedEditorState to flat BlockNodeAst[].
11
+ *
12
+ * Used for body content where we need the array directly
13
+ * rather than wrapped in a ContentAst.
14
+ */
15
+ export declare function mapEditorStateToBlocks(state: SerializedEditorState | null | undefined): BlockNodeAst[];
16
+ //# sourceMappingURL=content-mapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-mapper.d.ts","sourceRoot":"","sources":["../../src/ast/content-mapper.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAGxD;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,qBAAqB,GAAG,IAAI,GAAG,SAAS,GAC9C,UAAU,GAAG,IAAI,CAOnB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,qBAAqB,GAAG,IAAI,GAAG,SAAS,GAC9C,YAAY,EAAE,CAMhB"}
@@ -0,0 +1,11 @@
1
+ import { DocumentAst } from './types';
2
+ import { Lex4Document } from '../types/document';
3
+ import { VariableDefinition } from '../variables/types';
4
+ /**
5
+ * Serializes a Lex4Document into a clean, backend-friendly DocumentAst.
6
+ *
7
+ * @param document - The current editor document state
8
+ * @param variableDefinitions - Optional variable definitions for metadata
9
+ */
10
+ export declare function serializeDocument(document: Lex4Document, variableDefinitions?: VariableDefinition[]): DocumentAst;
11
+ //# sourceMappingURL=document-serializer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-serializer.d.ts","sourceRoot":"","sources":["../../src/ast/document-serializer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,WAAW,EAIZ,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAM7D;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,YAAY,EACtB,mBAAmB,GAAE,kBAAkB,EAAO,GAC7C,WAAW,CAiCb"}
@@ -0,0 +1,9 @@
1
+ export type { DocumentAst, PageFormatAst, MarginsAst, HeaderFooterConfigAst, DocumentMetadataAst, PageAst, ContentAst, BlockNodeAst, Alignment, ParagraphAst, HeadingAst, ListAst, ListItemAst, BlockQuoteAst, InlineNodeAst, TextMarks, TextRunAst, VariableAst, LineBreakAst, VariableDefinitionAst, SaveDocumentRequest, } from './types';
2
+ export { AST_VERSION } from './types';
3
+ export { mapInlineNode, mapInlineNodes, decodeFormatBitmask, extractFontFamily, extractFontSizePt, buildTextMarks, } from './inline-mapper';
4
+ export { mapBlockNode, mapBlockNodes } from './block-mapper';
5
+ export { mapEditorStateToContent, mapEditorStateToBlocks } from './content-mapper';
6
+ export { serializeDocument } from './document-serializer';
7
+ export { buildSavePayload, serializeDocumentJson } from './payload-builder';
8
+ export type { PayloadOptions } from './payload-builder';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ast/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,WAAW,EACX,aAAa,EACb,UAAU,EACV,qBAAqB,EACrB,mBAAmB,EACnB,OAAO,EACP,UAAU,EACV,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,UAAU,EACV,OAAO,EACP,WAAW,EACX,aAAa,EACb,aAAa,EACb,SAAS,EACT,UAAU,EACV,WAAW,EACX,YAAY,EACZ,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC,OAAO,EACL,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,GACf,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AACnF,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC5E,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { InlineNodeAst, TextMarks } from './types';
2
+ import { SerializedVariableNode } from '../variables/variable-node';
3
+ /** Serialized Lexical text node shape (subset of fields we need). */
4
+ interface SerializedTextNode {
5
+ type: 'text';
6
+ text: string;
7
+ format: number;
8
+ style?: string;
9
+ [key: string]: unknown;
10
+ }
11
+ interface SerializedLineBreak {
12
+ type: 'linebreak';
13
+ [key: string]: unknown;
14
+ }
15
+ type SerializedInlineNode = SerializedTextNode | SerializedVariableNode | SerializedLineBreak;
16
+ /**
17
+ * Decodes Lexical's format bitmask into named boolean marks.
18
+ */
19
+ export declare function decodeFormatBitmask(format: number): Pick<TextMarks, 'bold' | 'italic' | 'underline' | 'strikethrough'>;
20
+ /**
21
+ * Extracts font-family from a CSS style string.
22
+ */
23
+ export declare function extractFontFamily(style: string): string | undefined;
24
+ /**
25
+ * Extracts font-size (in pt) from a CSS style string.
26
+ */
27
+ export declare function extractFontSizePt(style: string): number | undefined;
28
+ /**
29
+ * Builds TextMarks from a Lexical format bitmask and style string.
30
+ */
31
+ export declare function buildTextMarks(format: number, style?: string): TextMarks | undefined;
32
+ /**
33
+ * Maps a single serialized Lexical inline node to an AST inline node.
34
+ */
35
+ export declare function mapInlineNode(node: SerializedInlineNode): InlineNodeAst;
36
+ /**
37
+ * Maps an array of serialized Lexical inline nodes to AST inline nodes.
38
+ */
39
+ export declare function mapInlineNodes(nodes: SerializedInlineNode[]): InlineNodeAst[];
40
+ export {};
41
+ //# sourceMappingURL=inline-mapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline-mapper.d.ts","sourceRoot":"","sources":["../../src/ast/inline-mapper.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAyC,MAAM,SAAS,CAAC;AAC/F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAQzE,qEAAqE;AACrE,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,UAAU,mBAAmB;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,KAAK,oBAAoB,GAAG,kBAAkB,GAAG,sBAAsB,GAAG,mBAAmB,CAAC;AAE9F;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,eAAe,CAAC,CAOtH;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGnE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGnE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAYpF;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,aAAa,CAYvE;AAsBD;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,CAE7E"}
@@ -0,0 +1,18 @@
1
+ import { DocumentAst, SaveDocumentRequest } from './types';
2
+ export interface PayloadOptions {
3
+ /** Target export format */
4
+ exportTarget?: 'pdf' | 'docx';
5
+ /** External document identifier */
6
+ documentId?: string;
7
+ /** Additional metadata for the backend */
8
+ metadata?: Record<string, string>;
9
+ }
10
+ /**
11
+ * Builds a SaveDocumentRequest payload from a DocumentAst.
12
+ */
13
+ export declare function buildSavePayload(ast: DocumentAst, options?: PayloadOptions): SaveDocumentRequest;
14
+ /**
15
+ * Serializes a DocumentAst to a JSON string.
16
+ */
17
+ export declare function serializeDocumentJson(ast: DocumentAst): string;
18
+ //# sourceMappingURL=payload-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payload-builder.d.ts","sourceRoot":"","sources":["../../src/ast/payload-builder.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAEhE,MAAM,WAAW,cAAc;IAC7B,2BAA2B;IAC3B,YAAY,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC9B,mCAAmC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,WAAW,EAChB,OAAO,CAAC,EAAE,cAAc,GACvB,mBAAmB,CAOrB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAE9D"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Document AST types — the backend integration contract.
3
+ *
4
+ * These types define the clean, Lexical-independent AST
5
+ * that consumers receive when calling getDocumentAst().
6
+ * Backend teams can use these types directly without any
7
+ * knowledge of Lexical or React internals.
8
+ *
9
+ * @version 1.0.0
10
+ */
11
+ export declare const AST_VERSION: "1.0.0";
12
+ export interface DocumentAst {
13
+ version: typeof AST_VERSION;
14
+ page: PageFormatAst;
15
+ headerFooter: HeaderFooterConfigAst;
16
+ pages: PageAst[];
17
+ metadata: DocumentMetadataAst;
18
+ }
19
+ export interface PageFormatAst {
20
+ format: 'A4';
21
+ widthMm: number;
22
+ heightMm: number;
23
+ margins: MarginsAst;
24
+ }
25
+ export interface MarginsAst {
26
+ topMm: number;
27
+ rightMm: number;
28
+ bottomMm: number;
29
+ leftMm: number;
30
+ }
31
+ export interface HeaderFooterConfigAst {
32
+ enabled: boolean;
33
+ pageCounterMode: 'none' | 'header' | 'footer' | 'both';
34
+ defaultHeader: ContentAst | null;
35
+ defaultFooter: ContentAst | null;
36
+ }
37
+ export interface DocumentMetadataAst {
38
+ variables: Record<string, VariableDefinitionAst>;
39
+ }
40
+ export interface PageAst {
41
+ pageIndex: number;
42
+ body: BlockNodeAst[];
43
+ header: ContentAst | null;
44
+ footer: ContentAst | null;
45
+ }
46
+ export interface ContentAst {
47
+ blocks: BlockNodeAst[];
48
+ }
49
+ export type BlockNodeAst = ParagraphAst | HeadingAst | ListAst | BlockQuoteAst;
50
+ export type Alignment = 'left' | 'center' | 'right' | 'justify';
51
+ export interface ParagraphAst {
52
+ type: 'paragraph';
53
+ alignment?: Alignment;
54
+ indent?: number;
55
+ children: InlineNodeAst[];
56
+ }
57
+ export interface HeadingAst {
58
+ type: 'heading';
59
+ level: 1 | 2 | 3 | 4 | 5;
60
+ alignment?: Alignment;
61
+ children: InlineNodeAst[];
62
+ }
63
+ export interface ListAst {
64
+ type: 'list';
65
+ listType: 'ordered' | 'unordered';
66
+ items: ListItemAst[];
67
+ }
68
+ export interface ListItemAst {
69
+ type: 'list-item';
70
+ children: InlineNodeAst[];
71
+ nestedList?: ListAst;
72
+ }
73
+ export interface BlockQuoteAst {
74
+ type: 'blockquote';
75
+ children: InlineNodeAst[];
76
+ }
77
+ export type InlineNodeAst = TextRunAst | VariableAst | LineBreakAst;
78
+ export interface TextMarks {
79
+ bold?: boolean;
80
+ italic?: boolean;
81
+ underline?: boolean;
82
+ strikethrough?: boolean;
83
+ fontFamily?: string;
84
+ fontSize?: number;
85
+ }
86
+ export interface TextRunAst {
87
+ type: 'text';
88
+ text: string;
89
+ marks?: TextMarks;
90
+ }
91
+ export interface VariableAst {
92
+ type: 'variable';
93
+ key: string;
94
+ marks?: TextMarks;
95
+ }
96
+ export interface LineBreakAst {
97
+ type: 'linebreak';
98
+ }
99
+ export interface VariableDefinitionAst {
100
+ key: string;
101
+ label: string;
102
+ description?: string;
103
+ valueType?: 'string' | 'number' | 'date' | 'boolean';
104
+ group?: string;
105
+ }
106
+ export interface SaveDocumentRequest {
107
+ document: DocumentAst;
108
+ exportTarget?: 'pdf' | 'docx';
109
+ documentId?: string;
110
+ metadata?: Record<string, string>;
111
+ }
112
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/ast/types.ts"],"names":[],"mappings":"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"}