smartrte-react 0.3.5 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +106 -14
  3. package/dist/adapters/domChecklistCommandBridge.js +1 -1
  4. package/dist/adapters/domCommandBridge.d.ts +2 -2
  5. package/dist/adapters/domCommandBridge.js +2 -2
  6. package/dist/adapters/domInlineAtomCommandBridge.d.ts +10 -9
  7. package/dist/adapters/domInlineAtomCommandBridge.js +154 -50
  8. package/dist/adapters/domInlineImageCommandBridge.d.ts +7 -7
  9. package/dist/adapters/domInlineImageCommandBridge.js +6 -72
  10. package/dist/adapters/domMoveCommandBridge.js +1 -1
  11. package/dist/adapters/domSelectionBridge.d.ts +3 -3
  12. package/dist/adapters/domSmartDocument.d.ts +4 -4
  13. package/dist/adapters/domSmartDocument.js +2 -2
  14. package/dist/adapters/domTableCommandBridge.d.ts +42 -16
  15. package/dist/adapters/domTableCommandBridge.js +203 -173
  16. package/dist/adapters/internalFlags.d.ts +0 -8
  17. package/dist/adapters/internalFlags.js +0 -24
  18. package/dist/adapters/legacyListShadowComparator.d.ts +1 -1
  19. package/dist/adapters/legacyListShadowComparator.js +1 -1
  20. package/dist/adapters/pdfPrint.d.ts +8 -0
  21. package/dist/adapters/pdfPrint.js +22 -0
  22. package/dist/adapters/shadowMode.d.ts +3 -3
  23. package/dist/adapters/shadowMode.js +1 -1
  24. package/dist/canonicalEditorRuntime.d.ts +53 -1
  25. package/dist/canonicalEditorRuntime.js +61 -4
  26. package/dist/capabilityPresets.d.ts +27 -0
  27. package/dist/capabilityPresets.js +21 -0
  28. package/dist/collabTransport.d.ts +66 -0
  29. package/dist/collabTransport.js +1 -0
  30. package/dist/commentProvider.d.ts +24 -0
  31. package/dist/commentProvider.js +1 -0
  32. package/dist/components/CanonicalAuthorityEditor.d.ts +35 -1
  33. package/dist/components/CanonicalAuthorityEditor.js +1086 -112
  34. package/dist/components/ClassicEditorAuthority.d.ts +93 -22
  35. package/dist/components/ClassicEditorAuthority.js +11 -43
  36. package/dist/components/ColorPickerPopover.d.ts +39 -0
  37. package/dist/components/ColorPickerPopover.js +285 -0
  38. package/dist/components/CommentMarkers.d.ts +25 -0
  39. package/dist/components/CommentMarkers.js +105 -0
  40. package/dist/components/CommentThreadPanel.d.ts +26 -0
  41. package/dist/components/CommentThreadPanel.js +47 -0
  42. package/dist/components/ContextMenu.d.ts +23 -0
  43. package/dist/components/ContextMenu.js +141 -0
  44. package/dist/components/FormulaLibraryPopover.d.ts +16 -0
  45. package/dist/components/FormulaLibraryPopover.js +107 -0
  46. package/dist/components/LinkEditorPopover.d.ts +3 -1
  47. package/dist/components/LinkEditorPopover.js +22 -6
  48. package/dist/components/MediaManager.d.ts +4 -20
  49. package/dist/components/MediaManager.js +35 -6
  50. package/dist/components/MediaOverlay.d.ts +36 -0
  51. package/dist/components/MediaOverlay.js +163 -0
  52. package/dist/components/SpecialCharacterPopover.d.ts +18 -0
  53. package/dist/components/SpecialCharacterPopover.js +99 -0
  54. package/dist/components/StructuralSuggestionMarkers.d.ts +20 -0
  55. package/dist/components/StructuralSuggestionMarkers.js +56 -0
  56. package/dist/components/SuggestionPanel.d.ts +26 -0
  57. package/dist/components/SuggestionPanel.js +33 -0
  58. package/dist/components/TableBorderPopover.d.ts +59 -0
  59. package/dist/components/TableBorderPopover.js +147 -0
  60. package/dist/components/TableResizeHandles.d.ts +25 -0
  61. package/dist/components/TableResizeHandles.js +301 -0
  62. package/dist/components/TableSizePickerPopover.d.ts +16 -0
  63. package/dist/components/TableSizePickerPopover.js +126 -0
  64. package/dist/components/ToolbarPrimitives.d.ts +129 -0
  65. package/dist/components/ToolbarPrimitives.js +146 -0
  66. package/dist/components/VersionHistoryPanel.d.ts +9 -0
  67. package/dist/components/VersionHistoryPanel.js +157 -0
  68. package/dist/formulaLibrary.d.ts +31 -0
  69. package/dist/formulaLibrary.js +90 -0
  70. package/dist/index.d.ts +22 -24
  71. package/dist/index.js +14 -11
  72. package/dist/mediaManagerAdapter.d.ts +12 -0
  73. package/dist/mediaManagerAdapter.js +34 -0
  74. package/dist/mediaProvider.d.ts +21 -1
  75. package/dist/specialCharacters.d.ts +22 -0
  76. package/dist/specialCharacters.js +86 -0
  77. package/dist/standalone/classic-editor-embed.js +19 -12
  78. package/dist/suggestionProvider.d.ts +17 -0
  79. package/dist/suggestionProvider.js +1 -0
  80. package/dist/theme.d.ts +1 -1
  81. package/dist/theme.js +133 -1
  82. package/dist/versionProvider.d.ts +29 -0
  83. package/dist/versionProvider.js +1 -0
  84. package/package.json +28 -14
  85. package/dist/adapters/canonicalInlineCommandBridge.d.ts +0 -15
  86. package/dist/adapters/canonicalInlineCommandBridge.js +0 -269
  87. package/dist/adapters/canonicalListCommandBridge.d.ts +0 -26
  88. package/dist/adapters/canonicalListCommandBridge.js +0 -430
  89. package/dist/adapters/coreBold.d.ts +0 -5
  90. package/dist/adapters/coreBold.js +0 -3
  91. package/dist/adapters/documentFormats.d.ts +0 -26
  92. package/dist/adapters/documentFormats.js +0 -49
  93. package/dist/adapters/docxFormat.d.ts +0 -5
  94. package/dist/adapters/docxFormat.js +0 -272
  95. package/dist/adapters/domBlockCommandBridge.d.ts +0 -19
  96. package/dist/adapters/domBlockCommandBridge.js +0 -79
  97. package/dist/adapters/inlineMarkCoreExecution.d.ts +0 -9
  98. package/dist/adapters/inlineMarkCoreExecution.js +0 -25
  99. package/dist/adapters/pdfFormat.d.ts +0 -23
  100. package/dist/adapters/pdfFormat.js +0 -204
  101. package/dist/adapters/portableDocxAtoms.d.ts +0 -4
  102. package/dist/adapters/portableDocxAtoms.js +0 -58
  103. package/dist/adapters/styledDocxFormat.d.ts +0 -8
  104. package/dist/adapters/styledDocxFormat.js +0 -190
  105. package/dist/builtInFormatDefinitions.d.ts +0 -5
  106. package/dist/builtInFormatDefinitions.js +0 -82
  107. package/dist/canonicalAuthorityFlag.d.ts +0 -32
  108. package/dist/canonicalAuthorityFlag.js +0 -49
  109. package/dist/canonicalClipboardRuntime.d.ts +0 -15
  110. package/dist/canonicalClipboardRuntime.js +0 -76
  111. package/dist/components/ClassicEditor.d.ts +0 -76
  112. package/dist/components/ClassicEditor.js +0 -6729
  113. package/dist/editorController.d.ts +0 -78
  114. package/dist/editorController.js +0 -298
  115. package/dist/formatFidelity.d.ts +0 -14
  116. package/dist/formatFidelity.js +0 -107
  117. package/dist/formatRuntime.d.ts +0 -50
  118. package/dist/formatRuntime.js +0 -20
  119. package/dist/pluginRuntime.d.ts +0 -68
  120. package/dist/pluginRuntime.js +0 -78
  121. package/dist/standalone/editor.js +0 -1068
  122. package/dist/test-harness/atomShadowComparator.d.ts +0 -14
  123. package/dist/test-harness/atomShadowComparator.js +0 -63
  124. package/dist/test-harness/blockShadowComparator.d.ts +0 -30
  125. package/dist/test-harness/blockShadowComparator.js +0 -140
  126. package/dist/test-harness/clipboardShadowComparator.d.ts +0 -12
  127. package/dist/test-harness/clipboardShadowComparator.js +0 -46
  128. package/dist/test-harness/inlineShadowComparator.d.ts +0 -32
  129. package/dist/test-harness/inlineShadowComparator.js +0 -152
  130. package/dist/test-harness/legacyAtomEngine.d.ts +0 -10
  131. package/dist/test-harness/legacyAtomEngine.js +0 -15
  132. package/dist/test-harness/legacyBlockEngine.d.ts +0 -31
  133. package/dist/test-harness/legacyBlockEngine.js +0 -31
  134. package/dist/test-harness/legacyClipboardEngine.d.ts +0 -14
  135. package/dist/test-harness/legacyClipboardEngine.js +0 -67
  136. package/dist/test-harness/legacyInlineEngine.d.ts +0 -19
  137. package/dist/test-harness/legacyInlineEngine.js +0 -49
  138. package/dist/test-harness/legacyTableEngine.d.ts +0 -12
  139. package/dist/test-harness/legacyTableEngine.js +0 -25
  140. package/dist/test-harness/tableShadowComparator.d.ts +0 -29
  141. package/dist/test-harness/tableShadowComparator.js +0 -101
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.0-beta.1
4
+
5
+ **BREAKING CHANGE.** `ClassicEditor` is now unconditionally the canonical, schema-driven editor (`CanonicalAuthorityEditor` underneath). The DOM-authoritative legacy editor and its rollback path have been fully removed; every published version through 0.3.4 defaulted to the DOM-authoritative implementation, so this changes real runtime behavior for every existing consumer, not just internals.
6
+
7
+ - Promote the canonical editing engine to sole production authority. Removed `LegacyClassicEditor` (the previous default), the runtime rollback flag, and all four of its DOM-authoritative command bridges.
8
+ - Add a documented, tested headless facade: `CanonicalEditorRuntime` / `SmartEditorHandle` (`createCanonicalEditorRuntime`) has zero React dependency and can drive a full editing session — mount, edit, checkpoint/restore, get/replace value — against a plain DOM element with no React runtime involved.
9
+ - Wire DOCX, PDF, and Markdown import/export to `smartrte-core`'s new format codecs, replacing the previous ad hoc, partially DOM-dependent adapters.
10
+ - Add live KaTeX rendering for formula content, including accessible MathML output.
11
+ - Depends on `smartrte-core@1.0.0-beta.1`.
12
+
3
13
  ## 0.3.5
4
14
 
5
15
  - Remove the always-on drag handle that appeared next to every line, list item, and table.
package/README.md CHANGED
@@ -41,7 +41,7 @@ pnpm add smartrte-react
41
41
  ## 🚀 Quick Start
42
42
 
43
43
  ### 🎮 Live Demo
44
-
44
+
45
45
  Try the editor instantly in your browser:
46
46
 
47
47
  - **[Live Demo](https://playground-k9l44ah7t-ayush1852017s-projects.vercel.app/)** (Deployed Version)
@@ -87,10 +87,37 @@ export default App;
87
87
  | `table` | `boolean` | `true` | Enable/disable table functionality |
88
88
  | `media` | `boolean` | `true` | Enable/disable media/image functionality |
89
89
  | `formula` | `boolean` | `true` | Enable/disable formula/LaTeX functionality |
90
+ | `features` | `CoreFeatureConfig` | standard preset | Enable or disable individual core plugins |
91
+ | `plugins` | `ReactEditorPlugin[]` | standard preset | Use an exact custom plugin set; replaces the standard preset |
92
+ | `formats` | `EditorFormatConfig` | all built-ins | Enable or disable HTML, Markdown, DOCX, and PDF adapters |
93
+ | `formatDefinitions` | `EditorFormatDefinition[]` | built-ins | Add or replace import/export adapters |
90
94
  | `mediaManager` | `MediaManagerAdapter` | `undefined` | Custom media manager for handling images |
95
+ | `fonts` | `{ name: string; value: string }[]` | Default web-safe fonts | Custom font list for the toolbar |
96
+ | `defaultFont` | `string` | `undefined` | Default font family for the editor content |
97
+ | `theme` | `"light" \| "dark"` | `"light"` | Built-in theme mode |
98
+ | `className` | `string` | `undefined` | Custom CSS class for theming via CSS variable overrides |
91
99
 
92
100
  ### Advanced Examples
93
101
 
102
+ #### Feature plugins
103
+
104
+ The editor uses the same plugin registry for core commands and React toolbar
105
+ state. For the architecture and custom plugin API, see
106
+ [`docs/PLUGIN_ARCHITECTURE.md`](../../docs/PLUGIN_ARCHITECTURE.md).
107
+
108
+ ```tsx
109
+ import { ClassicEditor } from 'smartrte-react';
110
+
111
+ <ClassicEditor
112
+ features={{
113
+ table: false,
114
+ media: false,
115
+ formula: false,
116
+ checklist: true,
117
+ }}
118
+ />
119
+ ```
120
+
94
121
  #### Complete Example with All Features
95
122
 
96
123
  ```tsx
@@ -314,23 +341,87 @@ const myMediaManager: MediaManagerAdapter = {
314
341
  };
315
342
  ```
316
343
 
317
- ## 🎨 Styling
344
+ ## 🎨 Theming & Dark Mode
345
+
346
+ The editor uses CSS custom properties (CSS variables) for all colors, making it fully customizable. No hardcoded colors — everything can be themed.
347
+
348
+ ### Quick Start: Dark Mode
349
+
350
+ ```tsx
351
+ <ClassicEditor theme="dark" onChange={handleChange} />
352
+ ```
353
+
354
+ ### Custom Themes via CSS
318
355
 
319
- The editor comes with built-in styles. You can customize the appearance by wrapping it in a container:
356
+ Apply a custom class and override any CSS variables:
320
357
 
321
358
  ```tsx
322
- <div style={{
323
- border: '1px solid #ddd',
324
- borderRadius: '8px',
325
- overflow: 'hidden',
326
- }}>
327
- <ClassicEditor
328
- value={content}
329
- onChange={setContent}
330
- />
331
- </div>
359
+ <ClassicEditor className="my-theme" onChange={handleChange} />
332
360
  ```
333
361
 
362
+ ```css
363
+ .my-theme {
364
+ --srte-bg: #1a1a2e;
365
+ --srte-text: #eaeaea;
366
+ --srte-border: #3a3a5c;
367
+ /* Override only the variables you need */
368
+ }
369
+ ```
370
+
371
+ ### Responding to System Preference
372
+
373
+ ```tsx
374
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
375
+
376
+ <ClassicEditor theme={prefersDark ? 'dark' : 'light'} />
377
+ ```
378
+
379
+ Or purely via CSS (without the `theme` prop):
380
+
381
+ ```css
382
+ @media (prefers-color-scheme: dark) {
383
+ .srte-editor {
384
+ --srte-bg: #1e1e1e;
385
+ --srte-text: #e0e0e0;
386
+ --srte-border: #3a3a3a;
387
+ /* ... */
388
+ }
389
+ }
390
+ ```
391
+
392
+ ### Available CSS Custom Properties
393
+
394
+ | Variable | Default (Light) | Description |
395
+ |---|---|---|
396
+ | `--srte-bg` | `#ffffff` | Editor container & content background |
397
+ | `--srte-text` | `#111111` | Primary UI text color |
398
+ | `--srte-text-muted` | `#4b5563` | Secondary/subtle text |
399
+ | `--srte-border` | `#dddddd` | Standard border color |
400
+ | `--srte-border-light` | `#eeeeee` | Light separator borders |
401
+ | `--srte-toolbar-bg` | `#ffffff` | Toolbar background |
402
+ | `--srte-input-bg` | `#ffffff` | Button, input, select backgrounds |
403
+ | `--srte-input-text` | `#111111` | Button, input, select text |
404
+ | `--srte-input-border` | `#e5e7eb` | Button, input, select borders |
405
+ | `--srte-modal-backdrop` | `rgba(0,0,0,0.35)` | Modal overlay background |
406
+ | `--srte-modal-bg` | `#ffffff` | Modal/dialog background |
407
+ | `--srte-modal-text` | `#000000` | Modal text color |
408
+ | `--srte-menu-bg` | `#ffffff` | Context menu background |
409
+ | `--srte-menu-text` | `#111111` | Context menu text |
410
+ | `--srte-menu-shadow` | `0 8px 24px rgba(0,0,0,0.18)` | Menu box-shadow |
411
+ | `--srte-accent` | `#1e90ff` | Accent/selection color |
412
+ | `--srte-accent-bg` | `rgba(30,144,255,0.15)` | Accent background (translucent) |
413
+ | `--srte-danger` | `#dc2626` | Destructive action color |
414
+ | `--srte-primary` | `#2563eb` | Primary action button color |
415
+ | `--srte-surface-subtle` | `#f3f4f6` | Subtle surface (dropzone, preset buttons) |
416
+ | `--srte-on-primary` | `#ffffff` | Text on primary/danger buttons |
417
+ | `--srte-cancel-bg` | `#f3f4f6` | Cancel button background |
418
+
419
+ ### Important Notes
420
+
421
+ - **User content colors are preserved** — colors set via the color picker (text/background) are inline styles on content elements and are not affected by theming.
422
+ - **Color picker swatches are not themed** — they display actual color values regardless of theme.
423
+ - **The theme only affects editor chrome** — toolbar, dialogs, menus, and container. User content remains unchanged.
424
+
334
425
  ## 🛠️ Development
335
426
 
336
427
  ### Prerequisites
@@ -626,13 +717,14 @@ See the list of [contributors](https://github.com/ayush1852017/smart-rte/contrib
626
717
 
627
718
  ## 🗺️ Roadmap
628
719
 
629
- ### Current Version (0.1.x)
720
+ ### Current Version (0.2.x)
630
721
 
631
722
  - ✅ Rich text editing
632
723
  - ✅ Table support
633
724
  - ✅ Formula support (LaTeX/KaTeX)
634
725
  - ✅ Media management
635
726
  - ✅ TypeScript support
727
+ - ✅ Dark mode & theming (CSS custom properties)
636
728
 
637
729
  ### Upcoming Features
638
730
 
@@ -1,4 +1,4 @@
1
- import { checklistPlugin, createSmartEditor, listPlugin } from "smartrte-core";
1
+ import { checklistPlugin, createSmartEditor, listPlugin } from "smartrte-core/legacy";
2
2
  import { smartDocumentFromHtml } from "./domSmartDocument.js";
3
3
  /**
4
4
  * Validates and executes a checklist mutation through core, then projects the
@@ -1,7 +1,7 @@
1
- import { type SmartRtePlugin, type SmartSelection } from "smartrte-core";
1
+ import { type SmartRtePlugin, type LegacySmartSelection } from "smartrte-core/legacy";
2
2
  export interface DomCommandResult {
3
3
  html: string;
4
- selection: SmartSelection;
4
+ selection: LegacySmartSelection;
5
5
  }
6
6
  /**
7
7
  * Executes one model command against the live editor boundary.
@@ -1,4 +1,4 @@
1
- import { createSmartEditor, } from "smartrte-core";
1
+ import { createSmartEditor, } from "smartrte-core/legacy";
2
2
  import { restoreSelectionToDom, selectionFromDom } from "./domSelectionBridge.js";
3
3
  import { serializeSmartDocument, smartDocumentFromEditorRoot, } from "./domSmartDocument.js";
4
4
  /**
@@ -9,7 +9,7 @@ import { serializeSmartDocument, smartDocumentFromEditorRoot, } from "./domSmart
9
9
  * the React host until the complete editor runtime is migrated.
10
10
  */
11
11
  export const executeDomCommand = (args) => {
12
- // These nodes are not represented by SmartDocument yet. Falling back is
12
+ // These nodes are not represented by LegacySmartDocument yet. Falling back is
13
13
  // mandatory so a supported command can never erase unsupported content.
14
14
  if (args.root.querySelector("img,video,audio,iframe")) {
15
15
  return null;
@@ -1,14 +1,15 @@
1
- import { type Path } from "smartrte-core";
2
- export declare const inlineAtomPathFromDom: (root: HTMLElement, atom: HTMLElement) => Path | null;
3
- /**
4
- * Runs deletion through the owning feature plugin and removes only the
5
- * validated live atom. Caret placement remains a DOM-host responsibility.
6
- */
7
- export declare const executeDomInlineAtomDelete: (root: HTMLElement, atom: HTMLElement) => boolean;
1
+ import { type Attrs } from "smartrte-core/foundation";
2
+ export declare const inlineAtomPathFromDom: (root: HTMLElement, atom: HTMLElement) => number[] | null;
8
3
  export interface DomFormulaInput {
9
4
  value: string;
10
5
  displayText?: string;
11
6
  }
12
- /** Validates formula insertion through core, then projects only the new atom. */
13
- export declare const executeDomFormulaInsert: (root: HTMLElement, input: DomFormulaInput, domSelection?: Selection | null) => HTMLElement | null;
7
+ export interface DomCanonicalAtomInput {
8
+ type: "image" | "formula";
9
+ attrs: Attrs;
10
+ }
11
+ export declare const executeDomCanonicalAtomInsert: (root: HTMLElement, input: DomCanonicalAtomInput, domSelection?: Selection | null) => HTMLElement | null;
12
+ export declare const executeDomFormulaInsert: (root: HTMLElement, input: DomFormulaInput, selection?: Selection | null) => HTMLElement | null;
13
+ export declare const executeDomInlineAtomDelete: (root: HTMLElement, atom: HTMLElement) => boolean;
14
+ export declare const executeDomCanonicalAtomUpdate: (root: HTMLElement, atom: HTMLElement, attrs: Attrs) => boolean;
14
15
  export declare const adjacentInlineAtom: (range: Range, direction: "backward" | "forward") => HTMLElement | null;
@@ -1,10 +1,10 @@
1
- import { createSmartEditor, formulaPlugin, getNodeAtPath, mediaPlugin, } from "smartrte-core";
2
- import { isEditorOnlyElement, pathForDomElement, selectionFromDom } from "./domSelectionBridge.js";
3
- import { smartDocumentFromEditorRoot } from "./domSmartDocument.js";
1
+ import { applyOperations, atomDeclarations, createNodeId, createScopeIndex, deleteAtom, foundationSchema, insertAtom, parseCanonicalListHtml, renderDocumentNaively, updateAtom, } from "smartrte-core/foundation";
2
+ import { isEditorOnlyElement, pathForDomElement } from "./domSelectionBridge.js";
4
3
  const atomType = (element) => {
5
- if (element.tagName.toLowerCase() === "img")
6
- return "inlineImage";
7
- if (element.hasAttribute("data-formula"))
4
+ const declared = element.getAttribute("data-smart-type");
5
+ if (declared === "image" || element.tagName.toLowerCase() === "img")
6
+ return "image";
7
+ if (declared === "formula" || element.hasAttribute("data-formula") || element.hasAttribute("data-smart-formula"))
8
8
  return "formula";
9
9
  return null;
10
10
  };
@@ -35,61 +35,165 @@ export const inlineAtomPathFromDom = (root, atom) => {
35
35
  const index = inlineUnits(leaf).indexOf(atom);
36
36
  return leafPath && index >= 0 ? [...leafPath, index] : null;
37
37
  };
38
- /**
39
- * Runs deletion through the owning feature plugin and removes only the
40
- * validated live atom. Caret placement remains a DOM-host responsibility.
41
- */
42
- export const executeDomInlineAtomDelete = (root, atom) => {
43
- const type = atomType(atom);
44
- const path = type && inlineAtomPathFromDom(root, atom);
45
- if (!type || !path)
46
- return false;
47
- const selection = selectionFromDom(root, root.ownerDocument.defaultView?.getSelection() || null) || { type: "node", path };
48
- const { document } = smartDocumentFromEditorRoot(root);
49
- const editor = createSmartEditor({
50
- state: { document, selection },
51
- plugins: type === "formula" ? [formulaPlugin] : [mediaPlugin],
38
+ const ownerSelector = "p,h1,h2,h3,h4,h5,h6";
39
+ const prepareOwner = (owner) => {
40
+ var _a;
41
+ (_a = owner.dataset).smartId || (_a.smartId = createNodeId());
42
+ owner.querySelectorAll("img,[data-formula],[data-smart-formula]").forEach((atom) => {
43
+ var _a, _b, _c;
44
+ (_a = atom.dataset).smartId || (_a.smartId = createNodeId());
45
+ if (atomType(atom) === "formula") {
46
+ atom.dataset.smartType = "formula";
47
+ atom.dataset.smartFormula = atom.dataset.smartFormula || atom.dataset.formula || atom.textContent || "";
48
+ (_b = atom.dataset).smartNotation || (_b.smartNotation = "latex");
49
+ atom.dataset.smartAtomic = "true";
50
+ }
51
+ else {
52
+ atom.dataset.smartType = "image";
53
+ (_c = atom.dataset).smartStatus || (_c.smartStatus = "ready");
54
+ atom.dataset.smartAtomic = "true";
55
+ }
52
56
  });
53
- const command = type === "formula" ? "formula.delete" : "image.delete-inline";
54
- if (!editor.execute(command, { path }))
57
+ };
58
+ const parseOwner = (owner) => {
59
+ prepareOwner(owner);
60
+ return parseCanonicalListHtml(owner.outerHTML);
61
+ };
62
+ const contentWidth = (node) => {
63
+ if (node.nodeType === node.TEXT_NODE)
64
+ return node.nodeValue?.length || 0;
65
+ if (node instanceof HTMLElement && atomType(node))
66
+ return 1;
67
+ if (node instanceof HTMLElement && isEditorOnlyElement(node))
68
+ return 0;
69
+ return [...node.childNodes].reduce((sum, child) => sum + contentWidth(child), 0);
70
+ };
71
+ const caretOffset = (owner, range) => {
72
+ const before = owner.ownerDocument.createRange();
73
+ before.selectNodeContents(owner);
74
+ try {
75
+ before.setEnd(range.startContainer, range.startOffset);
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ return contentWidth(before.cloneContents());
81
+ };
82
+ const renderOwner = (owner, document) => {
83
+ const projection = owner.ownerDocument.createElement("div");
84
+ renderDocumentNaively(projection, document);
85
+ const replacement = Array.from(projection.querySelectorAll("[data-smart-id]"))
86
+ .find((element) => element.dataset.smartId === owner.dataset.smartId);
87
+ if (!replacement)
55
88
  return false;
56
- atom.remove();
89
+ owner.replaceChildren(...Array.from(replacement.childNodes));
57
90
  return true;
58
91
  };
59
- /** Validates formula insertion through core, then projects only the new atom. */
60
- export const executeDomFormulaInsert = (root, input, domSelection = root.ownerDocument.defaultView?.getSelection() || null) => {
92
+ export const executeDomCanonicalAtomInsert = (root, input, domSelection = root.ownerDocument.defaultView?.getSelection() || null) => {
61
93
  if (!domSelection?.rangeCount || !domSelection.isCollapsed)
62
94
  return null;
63
- const modelSelection = selectionFromDom(root, domSelection);
64
- if (!modelSelection)
95
+ const range = domSelection.getRangeAt(0);
96
+ const element = range.startContainer instanceof Element ? range.startContainer : range.startContainer.parentElement;
97
+ const owner = element?.closest(ownerSelector);
98
+ if (!owner || !root.contains(owner))
65
99
  return null;
66
- const { document } = smartDocumentFromEditorRoot(root);
67
- const editor = createSmartEditor({
68
- state: { document, selection: modelSelection },
69
- plugins: [formulaPlugin],
70
- });
71
- if (!editor.execute("formula.insert", input))
100
+ const offset = caretOffset(owner, range);
101
+ if (offset === null)
72
102
  return null;
73
- const selectionAfter = editor.state.selection;
74
- if (selectionAfter.type !== "text")
103
+ const document = parseOwner(owner);
104
+ const declaration = atomDeclarations.find((entry) => entry.type === input.type);
105
+ const nodeId = createNodeId();
106
+ const index = createScopeIndex();
107
+ const operations = insertAtom(document, { kind: "empty", range: { from: { path: [0], offset }, to: { path: [0], offset } }, isolatingAncestorId: null, clamped: false }, {
108
+ declaration, nodeId, attrs: input.attrs, ownerId: owner.dataset.smartId, offset,
109
+ }, { schema: foundationSchema, positions: index.positions(document, foundationSchema) });
110
+ if (!operations.length || !renderOwner(owner, applyOperations(document, operations)))
75
111
  return null;
76
- const formulaPath = [
77
- ...selectionAfter.anchor.path.slice(0, -1),
78
- selectionAfter.anchor.path[selectionAfter.anchor.path.length - 1] - 1,
79
- ];
80
- const atom = getNodeAtPath(editor.state.document, formulaPath);
81
- if (atom?.type !== "formula")
112
+ const atom = Array.from(owner.querySelectorAll("[data-smart-id]"))
113
+ .find((element) => element.dataset.smartId === nodeId) || null;
114
+ if (!atom)
82
115
  return null;
83
- const range = domSelection.getRangeAt(0);
84
- const span = root.ownerDocument.createElement("span");
85
- span.dataset.formula = atom.value;
86
- span.textContent = atom.displayText ?? atom.value;
87
- range.insertNode(span);
88
- range.setStartAfter(span);
89
- range.collapse(true);
116
+ if (input.type === "formula") {
117
+ atom.dataset.formula = String(input.attrs.source || "");
118
+ atom.textContent = String(input.attrs.displayText || input.attrs.source || "");
119
+ }
120
+ else
121
+ atom.dataset.srteInline = "true";
122
+ const next = root.ownerDocument.createRange();
123
+ next.setStartAfter(atom);
124
+ next.collapse(true);
90
125
  domSelection.removeAllRanges();
91
- domSelection.addRange(range);
92
- return span;
126
+ domSelection.addRange(next);
127
+ return atom;
128
+ };
129
+ export const executeDomFormulaInsert = (root, input, selection) => executeDomCanonicalAtomInsert(root, { type: "formula", attrs: { source: input.value.trim(), notation: "latex", ...(input.displayText ? { displayText: input.displayText } : {}) } }, selection);
130
+ export const executeDomInlineAtomDelete = (root, atom) => {
131
+ const owner = atom.closest(ownerSelector);
132
+ if (!owner || !root.contains(owner) || !atomType(atom))
133
+ return false;
134
+ prepareOwner(owner);
135
+ const id = atom.dataset.smartId;
136
+ const document = parseOwner(owner);
137
+ const index = createScopeIndex();
138
+ const positions = index.positions(document, foundationSchema);
139
+ const range = positions.rangeOf(id);
140
+ if (!range)
141
+ return false;
142
+ const scope = { kind: "atomic-node", nodeId: id, inline: true, range, isolatingAncestorId: null, clamped: false };
143
+ const operations = deleteAtom(document, scope, {}, { schema: foundationSchema, positions });
144
+ return operations.length > 0 && renderOwner(owner, applyOperations(document, operations));
145
+ };
146
+ export const executeDomCanonicalAtomUpdate = (root, atom, attrs) => {
147
+ const owner = atom.closest(ownerSelector);
148
+ if (!owner || !root.contains(owner) || !atomType(atom))
149
+ return false;
150
+ prepareOwner(owner);
151
+ const id = atom.dataset.smartId;
152
+ const document = parseOwner(owner);
153
+ const index = createScopeIndex();
154
+ const positions = index.positions(document, foundationSchema);
155
+ const range = positions.rangeOf(id);
156
+ if (!range)
157
+ return false;
158
+ const scope = { kind: "atomic-node", nodeId: id, inline: true, range, isolatingAncestorId: null, clamped: false };
159
+ const operations = updateAtom(document, scope, { attrs }, { schema: foundationSchema, positions });
160
+ if (!operations.length)
161
+ return false;
162
+ const output = applyOperations(document, operations);
163
+ const nextPositions = createScopeIndex().positions(output, foundationSchema);
164
+ const resolved = nextPositions.positionOf(id);
165
+ const next = resolved?.parent.children?.find((child) => child.type !== "text" && "id" in child && child.id === id);
166
+ if (!next || next.type === "text")
167
+ return false;
168
+ if (atom instanceof HTMLImageElement) {
169
+ if (typeof next.attrs?.src === "string")
170
+ atom.src = next.attrs.src;
171
+ if (typeof next.attrs?.alt === "string")
172
+ atom.alt = next.attrs.alt;
173
+ if (typeof next.attrs?.title === "string")
174
+ atom.title = next.attrs.title;
175
+ if (Number(next.attrs?.width) > 0)
176
+ atom.width = Number(next.attrs?.width);
177
+ if (Number(next.attrs?.height) > 0)
178
+ atom.height = Number(next.attrs?.height);
179
+ const align = next.attrs?.align;
180
+ if (align === "center") {
181
+ atom.style.display = "block";
182
+ atom.style.margin = "0 auto";
183
+ atom.style.float = "none";
184
+ }
185
+ else if (align === "left" || align === "right") {
186
+ atom.style.display = "inline";
187
+ atom.style.float = align;
188
+ atom.style.margin = align === "left" ? "0 8px 8px 0" : "0 0 8px 8px";
189
+ }
190
+ atom.dataset.smartStatus = String(next.attrs?.status || "ready");
191
+ if (typeof next.attrs?.error === "string")
192
+ atom.dataset.smartError = next.attrs.error;
193
+ else
194
+ delete atom.dataset.smartError;
195
+ }
196
+ return true;
93
197
  };
94
198
  export const adjacentInlineAtom = (range, direction) => {
95
199
  if (!range.collapsed || range.startContainer.nodeType !== Node.ELEMENT_NODE)
@@ -1,14 +1,14 @@
1
1
  export interface DomInlineImageInput {
2
2
  src: string;
3
3
  alt?: string;
4
+ decorative?: boolean;
4
5
  title?: string;
5
6
  width?: number;
6
7
  height?: number;
8
+ align?: "center" | "left" | "right";
9
+ status?: "pending" | "ready" | "error";
10
+ uploadId?: string;
11
+ error?: string;
7
12
  }
8
- /**
9
- * Executes inline image insertion through core and projects only the resulting
10
- * atom into the existing DOM range. Unrelated DOM and metadata stay untouched.
11
- */
12
- export declare const executeDomInlineImageCommand: (root: HTMLElement, input: DomInlineImageInput, domSelection?: Selection | null) => HTMLImageElement | null;
13
- /** Validates an inline image attribute update through core, then mutates it in place. */
14
- export declare const executeDomInlineImageUpdate: (root: HTMLElement, image: HTMLImageElement, input: Omit<DomInlineImageInput, "src">) => boolean;
13
+ export declare const executeDomInlineImageCommand: (root: HTMLElement, input: DomInlineImageInput, selection?: Selection | null) => HTMLImageElement | null;
14
+ export declare const executeDomInlineImageUpdate: (root: HTMLElement, image: HTMLImageElement, input: Partial<DomInlineImageInput>) => boolean;
@@ -1,72 +1,6 @@
1
- import { createSmartEditor, getNodeAtPath, mediaPlugin, } from "smartrte-core";
2
- import { selectionFromDom } from "./domSelectionBridge.js";
3
- import { smartDocumentFromEditorRoot } from "./domSmartDocument.js";
4
- import { inlineAtomPathFromDom } from "./domInlineAtomCommandBridge.js";
5
- /**
6
- * Executes inline image insertion through core and projects only the resulting
7
- * atom into the existing DOM range. Unrelated DOM and metadata stay untouched.
8
- */
9
- export const executeDomInlineImageCommand = (root, input, domSelection = root.ownerDocument.defaultView?.getSelection() || null) => {
10
- if (!domSelection?.rangeCount || !domSelection.isCollapsed)
11
- return null;
12
- const modelSelection = selectionFromDom(root, domSelection);
13
- if (!modelSelection)
14
- return null;
15
- const { document } = smartDocumentFromEditorRoot(root);
16
- const editor = createSmartEditor({
17
- state: { document, selection: modelSelection },
18
- plugins: [mediaPlugin],
19
- });
20
- if (!editor.execute("image.insert-inline", input))
21
- return null;
22
- const selectionAfter = editor.state.selection;
23
- if (selectionAfter.type !== "text")
24
- return null;
25
- const imagePath = [
26
- ...selectionAfter.anchor.path.slice(0, -1),
27
- selectionAfter.anchor.path[selectionAfter.anchor.path.length - 1] - 1,
28
- ];
29
- const atom = getNodeAtPath(editor.state.document, imagePath);
30
- if (atom?.type !== "inlineImage")
31
- return null;
32
- const range = domSelection.getRangeAt(0);
33
- const image = root.ownerDocument.createElement("img");
34
- image.src = atom.src;
35
- image.alt = atom.alt || "";
36
- if (atom.title)
37
- image.title = atom.title;
38
- if (atom.width)
39
- image.width = atom.width;
40
- if (atom.height)
41
- image.height = atom.height;
42
- image.dataset.srteInline = "true";
43
- range.insertNode(image);
44
- range.setStartAfter(image);
45
- range.collapse(true);
46
- domSelection.removeAllRanges();
47
- domSelection.addRange(range);
48
- return image;
49
- };
50
- /** Validates an inline image attribute update through core, then mutates it in place. */
51
- export const executeDomInlineImageUpdate = (root, image, input) => {
52
- const path = inlineAtomPathFromDom(root, image);
53
- if (!path)
54
- return false;
55
- const selection = selectionFromDom(root, root.ownerDocument.defaultView?.getSelection() || null) || { type: "node", path };
56
- const { document } = smartDocumentFromEditorRoot(root);
57
- const editor = createSmartEditor({
58
- state: { document, selection },
59
- plugins: [mediaPlugin],
60
- });
61
- if (!editor.execute("image.update-inline", { path, ...input }))
62
- return false;
63
- if (input.alt !== undefined)
64
- image.alt = input.alt;
65
- if (input.title !== undefined)
66
- image.title = input.title;
67
- if (input.width !== undefined)
68
- image.width = input.width;
69
- if (input.height !== undefined)
70
- image.height = input.height;
71
- return true;
72
- };
1
+ import { executeDomCanonicalAtomInsert, executeDomCanonicalAtomUpdate } from "./domInlineAtomCommandBridge.js";
2
+ export const executeDomInlineImageCommand = (root, input, selection = root.ownerDocument.defaultView?.getSelection() || null) => executeDomCanonicalAtomInsert(root, {
3
+ type: "image",
4
+ attrs: { src: input.src, alt: input.alt ?? "", decorative: input.decorative === true, status: input.status || "ready", ...(input.uploadId ? { uploadId: input.uploadId } : {}), ...(input.error ? { error: input.error } : {}), ...(input.title ? { title: input.title } : {}), ...(input.width ? { width: input.width } : {}), ...(input.height ? { height: input.height } : {}) },
5
+ }, selection);
6
+ export const executeDomInlineImageUpdate = (root, image, input) => executeDomCanonicalAtomUpdate(root, image, input);
@@ -1,4 +1,4 @@
1
- import { createSmartEditor, movePlugin } from "smartrte-core";
1
+ import { createSmartEditor, movePlugin } from "smartrte-core/legacy";
2
2
  import { smartDocumentFromHtml } from "./domSmartDocument.js";
3
3
  /**
4
4
  * Executes model move/indent commands while preserving the existing DOM nodes.
@@ -1,8 +1,8 @@
1
- import type { Path, SmartSelection } from "smartrte-core";
1
+ import type { Path, LegacySmartSelection } from "smartrte-core/legacy";
2
2
  export declare const isEditorOnlyElement: (node: Element) => boolean;
3
3
  /** Returns the semantic model path for a DOM element at the adapter boundary. */
4
4
  export declare const pathForDomElement: (element: Element, editor: HTMLElement) => Path | null;
5
5
  /** Converts browser selection into a core selection without exposing editor UI nodes. */
6
- export declare const selectionFromDom: (editor: HTMLElement, selection: Selection | null) => SmartSelection | null;
6
+ export declare const selectionFromDom: (editor: HTMLElement, selection: Selection | null) => LegacySmartSelection | null;
7
7
  /** Restores a text selection from core paths after the editor DOM is rebuilt. */
8
- export declare const restoreSelectionToDom: (editor: HTMLElement, smartSelection: SmartSelection) => boolean;
8
+ export declare const restoreSelectionToDom: (editor: HTMLElement, smartSelection: LegacySmartSelection) => boolean;
@@ -1,10 +1,10 @@
1
- import { type SmartDocument } from "smartrte-core";
1
+ import { type LegacySmartDocument } from "smartrte-core/legacy";
2
2
  /** Removes editor UI before handing the document to the core parser boundary. */
3
3
  export declare const cleanEditorHtml: (root: HTMLElement) => string;
4
- export declare const smartDocumentFromHtml: (html: string, ownerDocument: Document) => SmartDocument;
4
+ export declare const smartDocumentFromHtml: (html: string, ownerDocument: Document) => LegacySmartDocument;
5
5
  export declare const smartDocumentFromEditorRoot: (root: HTMLElement) => {
6
- document: SmartDocument;
6
+ document: LegacySmartDocument;
7
7
  html: string;
8
8
  };
9
9
  /** Serializes the shadow-only model. It is not used for persisted editor HTML. */
10
- export declare const serializeSmartDocument: (document: SmartDocument) => string;
10
+ export declare const serializeSmartDocument: (document: LegacySmartDocument) => string;
@@ -1,5 +1,5 @@
1
- import { normalizeCompatibilityHtml, sanitizeLinkAttrs, } from "smartrte-core";
2
- import { isSmartListPreset } from "smartrte-core";
1
+ import { normalizeCompatibilityHtml, sanitizeLinkAttrs, } from "smartrte-core/legacy";
2
+ import { isSmartListPreset } from "smartrte-core/legacy";
3
3
  import { isEditorOnlyElement } from "./domSelectionBridge.js";
4
4
  const blockTags = new Set(["p", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "blockquote", "pre", "table", "img", "video", "audio"]);
5
5
  const unwrap = (element) => {