smartrte-react 0.3.4 → 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 (97) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +23 -0
  3. package/dist/adapters/domChecklistCommandBridge.d.ts +6 -0
  4. package/dist/adapters/domChecklistCommandBridge.js +33 -0
  5. package/dist/adapters/domCommandBridge.d.ts +19 -0
  6. package/dist/adapters/domCommandBridge.js +33 -0
  7. package/dist/adapters/domInlineAtomCommandBridge.d.ts +15 -0
  8. package/dist/adapters/domInlineAtomCommandBridge.js +205 -0
  9. package/dist/adapters/domInlineImageCommandBridge.d.ts +14 -0
  10. package/dist/adapters/domInlineImageCommandBridge.js +6 -0
  11. package/dist/adapters/domMoveCommandBridge.d.ts +7 -0
  12. package/dist/adapters/domMoveCommandBridge.js +54 -0
  13. package/dist/adapters/domSelectionBridge.d.ts +5 -3
  14. package/dist/adapters/domSelectionBridge.js +96 -44
  15. package/dist/adapters/domSmartDocument.d.ts +4 -4
  16. package/dist/adapters/domSmartDocument.js +159 -17
  17. package/dist/adapters/domTableCommandBridge.d.ts +61 -0
  18. package/dist/adapters/domTableCommandBridge.js +231 -0
  19. package/dist/adapters/internalFlags.d.ts +0 -8
  20. package/dist/adapters/internalFlags.js +0 -24
  21. package/dist/adapters/legacyListShadowComparator.d.ts +26 -0
  22. package/dist/adapters/legacyListShadowComparator.js +316 -0
  23. package/dist/adapters/pdfPrint.d.ts +8 -0
  24. package/dist/adapters/pdfPrint.js +22 -0
  25. package/dist/adapters/shadowMode.d.ts +3 -3
  26. package/dist/adapters/shadowMode.js +1 -1
  27. package/dist/canonicalEditorRuntime.d.ts +129 -0
  28. package/dist/canonicalEditorRuntime.js +331 -0
  29. package/dist/capabilityPresets.d.ts +27 -0
  30. package/dist/capabilityPresets.js +21 -0
  31. package/dist/collabTransport.d.ts +66 -0
  32. package/dist/collabTransport.js +1 -0
  33. package/dist/commentProvider.d.ts +24 -0
  34. package/dist/commentProvider.js +1 -0
  35. package/dist/components/CanonicalAuthorityEditor.d.ts +59 -0
  36. package/dist/components/CanonicalAuthorityEditor.js +1492 -0
  37. package/dist/components/ClassicEditorAuthority.d.ts +109 -0
  38. package/dist/components/ClassicEditorAuthority.js +17 -0
  39. package/dist/components/ColorPickerPopover.d.ts +39 -0
  40. package/dist/components/ColorPickerPopover.js +285 -0
  41. package/dist/components/CommentMarkers.d.ts +25 -0
  42. package/dist/components/CommentMarkers.js +105 -0
  43. package/dist/components/CommentThreadPanel.d.ts +26 -0
  44. package/dist/components/CommentThreadPanel.js +47 -0
  45. package/dist/components/ContextMenu.d.ts +23 -0
  46. package/dist/components/ContextMenu.js +141 -0
  47. package/dist/components/FormulaLibraryPopover.d.ts +16 -0
  48. package/dist/components/FormulaLibraryPopover.js +107 -0
  49. package/dist/components/LinkEditorPopover.d.ts +3 -1
  50. package/dist/components/LinkEditorPopover.js +22 -6
  51. package/dist/components/MediaManager.d.ts +4 -20
  52. package/dist/components/MediaManager.js +35 -6
  53. package/dist/components/MediaOverlay.d.ts +36 -0
  54. package/dist/components/MediaOverlay.js +163 -0
  55. package/dist/components/MediaPicker.d.ts +10 -0
  56. package/dist/components/MediaPicker.js +16 -0
  57. package/dist/components/SpecialCharacterPopover.d.ts +18 -0
  58. package/dist/components/SpecialCharacterPopover.js +99 -0
  59. package/dist/components/StructuralSuggestionMarkers.d.ts +20 -0
  60. package/dist/components/StructuralSuggestionMarkers.js +56 -0
  61. package/dist/components/SuggestionPanel.d.ts +26 -0
  62. package/dist/components/SuggestionPanel.js +33 -0
  63. package/dist/components/TableBorderPopover.d.ts +59 -0
  64. package/dist/components/TableBorderPopover.js +147 -0
  65. package/dist/components/TableResizeHandles.d.ts +25 -0
  66. package/dist/components/TableResizeHandles.js +301 -0
  67. package/dist/components/TableSizePickerPopover.d.ts +16 -0
  68. package/dist/components/TableSizePickerPopover.js +126 -0
  69. package/dist/components/ToolbarPrimitives.d.ts +129 -0
  70. package/dist/components/ToolbarPrimitives.js +146 -0
  71. package/dist/components/VersionHistoryPanel.d.ts +9 -0
  72. package/dist/components/VersionHistoryPanel.js +157 -0
  73. package/dist/formulaLibrary.d.ts +31 -0
  74. package/dist/formulaLibrary.js +90 -0
  75. package/dist/index.d.ts +23 -1
  76. package/dist/index.js +14 -1
  77. package/dist/mediaManagerAdapter.d.ts +12 -0
  78. package/dist/mediaManagerAdapter.js +34 -0
  79. package/dist/mediaProvider.d.ts +59 -0
  80. package/dist/mediaProvider.js +1 -0
  81. package/dist/specialCharacters.d.ts +22 -0
  82. package/dist/specialCharacters.js +86 -0
  83. package/dist/standalone/classic-editor-embed.js +19 -12
  84. package/dist/suggestionProvider.d.ts +17 -0
  85. package/dist/suggestionProvider.js +1 -0
  86. package/dist/theme.d.ts +1 -1
  87. package/dist/theme.js +161 -1
  88. package/dist/versionProvider.d.ts +29 -0
  89. package/dist/versionProvider.js +1 -0
  90. package/package.json +28 -14
  91. package/dist/adapters/coreBold.d.ts +0 -5
  92. package/dist/adapters/coreBold.js +0 -3
  93. package/dist/adapters/inlineMarkCoreExecution.d.ts +0 -9
  94. package/dist/adapters/inlineMarkCoreExecution.js +0 -25
  95. package/dist/components/ClassicEditor.d.ts +0 -66
  96. package/dist/components/ClassicEditor.js +0 -6617
  97. package/dist/standalone/editor.js +0 -841
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
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
+
13
+ ## 0.3.5
14
+
15
+ - Remove the always-on drag handle that appeared next to every line, list item, and table.
16
+ - Keep list-style and blockquote changes scoped to the selection instead of bleeding into unrelated parts of the document.
17
+ - Fix blockquote and code block corrupting each other, or losing content, when toggled in sequence without reselecting text in between.
18
+ - Preserve code blocks and blockquotes nested inside each other, including on list items, instead of one silently stripping the other.
19
+ - Fix the toolbar's active-state indicators (and the code block toggle itself) going stale on list items after removing an enclosing blockquote.
20
+ - Stop pasted or existing blockquotes/code blocks from losing their border and internal spacing when the source HTML carries conflicting inline styles.
21
+
3
22
  ## 0.3.4
4
23
 
5
24
  - Make foreground and background colours deterministic across paragraphs, lists, and table-cell text selections.
package/README.md CHANGED
@@ -87,6 +87,10 @@ 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 |
91
95
  | `fonts` | `{ name: string; value: string }[]` | Default web-safe fonts | Custom font list for the toolbar |
92
96
  | `defaultFont` | `string` | `undefined` | Default font family for the editor content |
@@ -95,6 +99,25 @@ export default App;
95
99
 
96
100
  ### Advanced Examples
97
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
+
98
121
  #### Complete Example with All Features
99
122
 
100
123
  ```tsx
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Validates and executes a checklist mutation through core, then projects the
3
+ * changed attribute onto the existing DOM item. Keeping the subtree in place
4
+ * preserves focus, selection, event targets, and framework-owned DOM identity.
5
+ */
6
+ export declare const executeDomChecklistItemCommand: (list: HTMLElement, item: HTMLElement, checked?: boolean) => HTMLElement | null;
@@ -0,0 +1,33 @@
1
+ import { checklistPlugin, createSmartEditor, listPlugin } from "smartrte-core/legacy";
2
+ import { smartDocumentFromHtml } from "./domSmartDocument.js";
3
+ /**
4
+ * Validates and executes a checklist mutation through core, then projects the
5
+ * changed attribute onto the existing DOM item. Keeping the subtree in place
6
+ * preserves focus, selection, event targets, and framework-owned DOM identity.
7
+ */
8
+ export const executeDomChecklistItemCommand = (list, item, checked) => {
9
+ if (list.dataset.srteChecklist !== "true" || item.parentElement !== list)
10
+ return null;
11
+ const items = Array.from(list.children).filter((child) => child instanceof HTMLElement && child.tagName === "LI");
12
+ const index = items.indexOf(item);
13
+ if (index < 0)
14
+ return null;
15
+ const document = smartDocumentFromHtml(list.outerHTML, list.ownerDocument);
16
+ if (document.children.length !== 1 || document.children[0].type !== "list")
17
+ return null;
18
+ const editor = createSmartEditor({
19
+ state: { document, selection: { type: "node", path: [0, index] } },
20
+ plugins: [listPlugin, checklistPlugin],
21
+ });
22
+ if (!editor.execute("checklist.set-checked", { path: [0, index], checked }))
23
+ return null;
24
+ const nextList = editor.state.document.children[0];
25
+ if (nextList.type !== "list")
26
+ return null;
27
+ const nextItem = nextList.children[index];
28
+ if (!nextItem || nextItem.type !== "listItem")
29
+ return null;
30
+ item.dataset.checked = nextItem.checked ? "true" : "false";
31
+ item.removeAttribute("data-srte-checked");
32
+ return list;
33
+ };
@@ -0,0 +1,19 @@
1
+ import { type SmartRtePlugin, type LegacySmartSelection } from "smartrte-core/legacy";
2
+ export interface DomCommandResult {
3
+ html: string;
4
+ selection: LegacySmartSelection;
5
+ }
6
+ /**
7
+ * Executes one model command against the live editor boundary.
8
+ *
9
+ * The DOM is parsed once, the command runs entirely in core, and the resulting
10
+ * document replaces the DOM once. History and change emission remain owned by
11
+ * the React host until the complete editor runtime is migrated.
12
+ */
13
+ export declare const executeDomCommand: <Input>(args: {
14
+ root: HTMLElement;
15
+ plugins: SmartRtePlugin[];
16
+ commandId: string;
17
+ input?: Input;
18
+ selection?: Selection | null;
19
+ }) => DomCommandResult | null;
@@ -0,0 +1,33 @@
1
+ import { createSmartEditor, } from "smartrte-core/legacy";
2
+ import { restoreSelectionToDom, selectionFromDom } from "./domSelectionBridge.js";
3
+ import { serializeSmartDocument, smartDocumentFromEditorRoot, } from "./domSmartDocument.js";
4
+ /**
5
+ * Executes one model command against the live editor boundary.
6
+ *
7
+ * The DOM is parsed once, the command runs entirely in core, and the resulting
8
+ * document replaces the DOM once. History and change emission remain owned by
9
+ * the React host until the complete editor runtime is migrated.
10
+ */
11
+ export const executeDomCommand = (args) => {
12
+ // These nodes are not represented by LegacySmartDocument yet. Falling back is
13
+ // mandatory so a supported command can never erase unsupported content.
14
+ if (args.root.querySelector("img,video,audio,iframe")) {
15
+ return null;
16
+ }
17
+ const selection = selectionFromDom(args.root, args.selection === undefined
18
+ ? args.root.ownerDocument.defaultView?.getSelection() || null
19
+ : args.selection);
20
+ if (!selection)
21
+ return null;
22
+ const { document } = smartDocumentFromEditorRoot(args.root);
23
+ const editor = createSmartEditor({
24
+ state: { document, selection },
25
+ plugins: args.plugins,
26
+ });
27
+ if (!editor.execute(args.commandId, args.input))
28
+ return null;
29
+ const html = serializeSmartDocument(editor.state.document);
30
+ args.root.innerHTML = html;
31
+ restoreSelectionToDom(args.root, editor.state.selection);
32
+ return { html, selection: editor.state.selection };
33
+ };
@@ -0,0 +1,15 @@
1
+ import { type Attrs } from "smartrte-core/foundation";
2
+ export declare const inlineAtomPathFromDom: (root: HTMLElement, atom: HTMLElement) => number[] | null;
3
+ export interface DomFormulaInput {
4
+ value: string;
5
+ displayText?: string;
6
+ }
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;
15
+ export declare const adjacentInlineAtom: (range: Range, direction: "backward" | "forward") => HTMLElement | null;
@@ -0,0 +1,205 @@
1
+ import { applyOperations, atomDeclarations, createNodeId, createScopeIndex, deleteAtom, foundationSchema, insertAtom, parseCanonicalListHtml, renderDocumentNaively, updateAtom, } from "smartrte-core/foundation";
2
+ import { isEditorOnlyElement, pathForDomElement } from "./domSelectionBridge.js";
3
+ const atomType = (element) => {
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
+ return "formula";
9
+ return null;
10
+ };
11
+ const inlineUnits = (root) => {
12
+ const units = [];
13
+ const visit = (node) => {
14
+ if (node.nodeType === Node.TEXT_NODE) {
15
+ if (node.textContent)
16
+ units.push(node);
17
+ return;
18
+ }
19
+ if (!(node instanceof Element) || isEditorOnlyElement(node))
20
+ return;
21
+ if (atomType(node)) {
22
+ units.push(node);
23
+ return;
24
+ }
25
+ Array.from(node.childNodes).forEach(visit);
26
+ };
27
+ Array.from(root.childNodes).forEach(visit);
28
+ return units;
29
+ };
30
+ export const inlineAtomPathFromDom = (root, atom) => {
31
+ const leaf = atom.closest("p,h1,h2,h3,h4,h5,h6");
32
+ if (!leaf || !root.contains(leaf))
33
+ return null;
34
+ const leafPath = pathForDomElement(leaf, root);
35
+ const index = inlineUnits(leaf).indexOf(atom);
36
+ return leafPath && index >= 0 ? [...leafPath, index] : null;
37
+ };
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
+ }
56
+ });
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)
88
+ return false;
89
+ owner.replaceChildren(...Array.from(replacement.childNodes));
90
+ return true;
91
+ };
92
+ export const executeDomCanonicalAtomInsert = (root, input, domSelection = root.ownerDocument.defaultView?.getSelection() || null) => {
93
+ if (!domSelection?.rangeCount || !domSelection.isCollapsed)
94
+ return null;
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))
99
+ return null;
100
+ const offset = caretOffset(owner, range);
101
+ if (offset === null)
102
+ return null;
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)))
111
+ return null;
112
+ const atom = Array.from(owner.querySelectorAll("[data-smart-id]"))
113
+ .find((element) => element.dataset.smartId === nodeId) || null;
114
+ if (!atom)
115
+ return null;
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);
125
+ domSelection.removeAllRanges();
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;
197
+ };
198
+ export const adjacentInlineAtom = (range, direction) => {
199
+ if (!range.collapsed || range.startContainer.nodeType !== Node.ELEMENT_NODE)
200
+ return null;
201
+ const container = range.startContainer;
202
+ const index = direction === "backward" ? range.startOffset - 1 : range.startOffset;
203
+ const candidate = container.childNodes[index];
204
+ return candidate instanceof HTMLElement && atomType(candidate) ? candidate : null;
205
+ };
@@ -0,0 +1,14 @@
1
+ export interface DomInlineImageInput {
2
+ src: string;
3
+ alt?: string;
4
+ decorative?: boolean;
5
+ title?: string;
6
+ width?: number;
7
+ height?: number;
8
+ align?: "center" | "left" | "right";
9
+ status?: "pending" | "ready" | "error";
10
+ uploadId?: string;
11
+ error?: string;
12
+ }
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;
@@ -0,0 +1,6 @@
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);
@@ -0,0 +1,7 @@
1
+ export type DomMoveDirection = "up" | "down" | "left" | "right";
2
+ /**
3
+ * Executes model move/indent commands while preserving the existing DOM nodes.
4
+ * Null means the DOM shape is outside this adapter; false means a valid command
5
+ * was disabled at a boundary.
6
+ */
7
+ export declare const executeDomMoveCommand: (blocks: readonly HTMLElement[], direction: DomMoveDirection, beforeMutation?: () => void) => HTMLElement[] | false | null;
@@ -0,0 +1,54 @@
1
+ import { createSmartEditor, movePlugin } from "smartrte-core/legacy";
2
+ import { smartDocumentFromHtml } from "./domSmartDocument.js";
3
+ /**
4
+ * Executes model move/indent commands while preserving the existing DOM nodes.
5
+ * Null means the DOM shape is outside this adapter; false means a valid command
6
+ * was disabled at a boundary.
7
+ */
8
+ export const executeDomMoveCommand = (blocks, direction, beforeMutation) => {
9
+ if (!blocks.length || !blocks[0].parentElement)
10
+ return null;
11
+ const parent = blocks[0].parentElement;
12
+ if (!blocks.every((block) => block.parentElement === parent))
13
+ return null;
14
+ const siblings = Array.from(parent.children);
15
+ const indexes = blocks.map((block) => siblings.indexOf(block));
16
+ if (indexes.some((index, position) => index < 0 || (position > 0 && index !== indexes[position - 1] + 1))) {
17
+ return null;
18
+ }
19
+ const document = smartDocumentFromHtml(siblings.map((sibling) => sibling.outerHTML).join(""), parent.ownerDocument);
20
+ if (document.children.length !== siblings.length)
21
+ return null;
22
+ const editor = createSmartEditor({
23
+ state: { document, selection: { type: "all" } },
24
+ plugins: [movePlugin],
25
+ });
26
+ const commandId = direction === "up" || direction === "down" ? "block.move" : "block.indent";
27
+ const input = {
28
+ parentPath: [],
29
+ blockIndexes: indexes,
30
+ direction: direction === "left" ? "outdent" : direction === "right" ? "indent" : direction,
31
+ };
32
+ if (!editor.execute(commandId, input))
33
+ return false;
34
+ beforeMutation?.();
35
+ if (direction === "left" || direction === "right") {
36
+ indexes.forEach((index, position) => {
37
+ const indent = editor.state.document.children[index].indent || 0;
38
+ blocks[position].style.marginLeft = indent ? `${indent * 24}px` : "";
39
+ if (!blocks[position].getAttribute("style"))
40
+ blocks[position].removeAttribute("style");
41
+ });
42
+ return [...blocks];
43
+ }
44
+ if (direction === "up") {
45
+ const previous = siblings[indexes[0] - 1];
46
+ blocks.forEach((block) => parent.insertBefore(block, previous));
47
+ }
48
+ else {
49
+ const next = siblings[indexes[indexes.length - 1] + 1];
50
+ const reference = next.nextSibling;
51
+ blocks.forEach((block) => parent.insertBefore(block, reference));
52
+ }
53
+ return [...blocks];
54
+ };
@@ -1,6 +1,8 @@
1
- import type { SmartSelection } from "smartrte-core";
1
+ import type { Path, LegacySmartSelection } from "smartrte-core/legacy";
2
2
  export declare const isEditorOnlyElement: (node: Element) => boolean;
3
+ /** Returns the semantic model path for a DOM element at the adapter boundary. */
4
+ export declare const pathForDomElement: (element: Element, editor: HTMLElement) => Path | null;
3
5
  /** Converts browser selection into a core selection without exposing editor UI nodes. */
4
- export declare const selectionFromDom: (editor: HTMLElement, selection: Selection | null) => SmartSelection | null;
6
+ export declare const selectionFromDom: (editor: HTMLElement, selection: Selection | null) => LegacySmartSelection | null;
5
7
  /** Restores a text selection from core paths after the editor DOM is rebuilt. */
6
- export declare const restoreSelectionToDom: (editor: HTMLElement, smartSelection: SmartSelection) => boolean;
8
+ export declare const restoreSelectionToDom: (editor: HTMLElement, smartSelection: LegacySmartSelection) => boolean;