smartrte-react 0.3.3 → 0.3.5

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 (87) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +14 -83
  3. package/dist/adapters/canonicalInlineCommandBridge.d.ts +15 -0
  4. package/dist/adapters/canonicalInlineCommandBridge.js +269 -0
  5. package/dist/adapters/canonicalListCommandBridge.d.ts +26 -0
  6. package/dist/adapters/canonicalListCommandBridge.js +430 -0
  7. package/dist/adapters/documentFormats.d.ts +26 -0
  8. package/dist/adapters/documentFormats.js +49 -0
  9. package/dist/adapters/docxFormat.d.ts +5 -0
  10. package/dist/adapters/docxFormat.js +272 -0
  11. package/dist/adapters/domBlockCommandBridge.d.ts +19 -0
  12. package/dist/adapters/domBlockCommandBridge.js +79 -0
  13. package/dist/adapters/domChecklistCommandBridge.d.ts +6 -0
  14. package/dist/adapters/domChecklistCommandBridge.js +33 -0
  15. package/dist/adapters/domCommandBridge.d.ts +19 -0
  16. package/dist/adapters/domCommandBridge.js +33 -0
  17. package/dist/adapters/domInlineAtomCommandBridge.d.ts +14 -0
  18. package/dist/adapters/domInlineAtomCommandBridge.js +101 -0
  19. package/dist/adapters/domInlineImageCommandBridge.d.ts +14 -0
  20. package/dist/adapters/domInlineImageCommandBridge.js +72 -0
  21. package/dist/adapters/domMoveCommandBridge.d.ts +7 -0
  22. package/dist/adapters/domMoveCommandBridge.js +54 -0
  23. package/dist/adapters/domSelectionBridge.d.ts +3 -1
  24. package/dist/adapters/domSelectionBridge.js +96 -44
  25. package/dist/adapters/domSmartDocument.js +158 -16
  26. package/dist/adapters/domTableCommandBridge.d.ts +35 -0
  27. package/dist/adapters/domTableCommandBridge.js +201 -0
  28. package/dist/adapters/legacyListShadowComparator.d.ts +26 -0
  29. package/dist/adapters/legacyListShadowComparator.js +316 -0
  30. package/dist/adapters/pdfFormat.d.ts +23 -0
  31. package/dist/adapters/pdfFormat.js +204 -0
  32. package/dist/adapters/portableDocxAtoms.d.ts +4 -0
  33. package/dist/adapters/portableDocxAtoms.js +58 -0
  34. package/dist/adapters/styledDocxFormat.d.ts +8 -0
  35. package/dist/adapters/styledDocxFormat.js +190 -0
  36. package/dist/builtInFormatDefinitions.d.ts +5 -0
  37. package/dist/builtInFormatDefinitions.js +82 -0
  38. package/dist/canonicalAuthorityFlag.d.ts +32 -0
  39. package/dist/canonicalAuthorityFlag.js +49 -0
  40. package/dist/canonicalClipboardRuntime.d.ts +15 -0
  41. package/dist/canonicalClipboardRuntime.js +76 -0
  42. package/dist/canonicalEditorRuntime.d.ts +77 -0
  43. package/dist/canonicalEditorRuntime.js +274 -0
  44. package/dist/components/CanonicalAuthorityEditor.d.ts +25 -0
  45. package/dist/components/CanonicalAuthorityEditor.js +518 -0
  46. package/dist/components/ClassicEditor.d.ts +13 -3
  47. package/dist/components/ClassicEditor.js +1309 -1159
  48. package/dist/components/ClassicEditorAuthority.d.ts +38 -0
  49. package/dist/components/ClassicEditorAuthority.js +49 -0
  50. package/dist/components/MediaPicker.d.ts +10 -0
  51. package/dist/components/MediaPicker.js +16 -0
  52. package/dist/editorController.d.ts +78 -0
  53. package/dist/editorController.js +298 -0
  54. package/dist/formatFidelity.d.ts +14 -0
  55. package/dist/formatFidelity.js +107 -0
  56. package/dist/formatRuntime.d.ts +50 -0
  57. package/dist/formatRuntime.js +20 -0
  58. package/dist/index.d.ts +24 -0
  59. package/dist/index.js +10 -0
  60. package/dist/mediaProvider.d.ts +39 -0
  61. package/dist/mediaProvider.js +1 -0
  62. package/dist/pluginRuntime.d.ts +68 -0
  63. package/dist/pluginRuntime.js +78 -0
  64. package/dist/standalone/editor.js +722 -495
  65. package/dist/test-harness/atomShadowComparator.d.ts +14 -0
  66. package/dist/test-harness/atomShadowComparator.js +63 -0
  67. package/dist/test-harness/blockShadowComparator.d.ts +30 -0
  68. package/dist/test-harness/blockShadowComparator.js +140 -0
  69. package/dist/test-harness/clipboardShadowComparator.d.ts +12 -0
  70. package/dist/test-harness/clipboardShadowComparator.js +46 -0
  71. package/dist/test-harness/inlineShadowComparator.d.ts +32 -0
  72. package/dist/test-harness/inlineShadowComparator.js +152 -0
  73. package/dist/test-harness/legacyAtomEngine.d.ts +10 -0
  74. package/dist/test-harness/legacyAtomEngine.js +15 -0
  75. package/dist/test-harness/legacyBlockEngine.d.ts +31 -0
  76. package/dist/test-harness/legacyBlockEngine.js +31 -0
  77. package/dist/test-harness/legacyClipboardEngine.d.ts +14 -0
  78. package/dist/test-harness/legacyClipboardEngine.js +67 -0
  79. package/dist/test-harness/legacyInlineEngine.d.ts +19 -0
  80. package/dist/test-harness/legacyInlineEngine.js +49 -0
  81. package/dist/test-harness/legacyTableEngine.d.ts +12 -0
  82. package/dist/test-harness/legacyTableEngine.js +25 -0
  83. package/dist/test-harness/tableShadowComparator.d.ts +29 -0
  84. package/dist/test-harness/tableShadowComparator.js +101 -0
  85. package/dist/theme.d.ts +1 -1
  86. package/dist/theme.js +28 -6
  87. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.5
4
+
5
+ - Remove the always-on drag handle that appeared next to every line, list item, and table.
6
+ - Keep list-style and blockquote changes scoped to the selection instead of bleeding into unrelated parts of the document.
7
+ - Fix blockquote and code block corrupting each other, or losing content, when toggled in sequence without reselecting text in between.
8
+ - Preserve code blocks and blockquotes nested inside each other, including on list items, instead of one silently stripping the other.
9
+ - Fix the toolbar's active-state indicators (and the code block toggle itself) going stale on list items after removing an enclosing blockquote.
10
+ - Stop pasted or existing blockquotes/code blocks from losing their border and internal spacing when the source HTML carries conflicting inline styles.
11
+
12
+ ## 0.3.4
13
+
14
+ - Make foreground and background colours deterministic across paragraphs, lists, and table-cell text selections.
15
+ - Preserve foreground colour when highlighting text and render authored colours correctly in dark mode.
16
+ - Show the effective foreground and background colours at the caret in the toolbar and picker.
17
+ - Keep the table-cell fill picker and context menu open during live colour dragging, including multi-cell selections.
18
+
3
19
  ## 0.3.3
4
20
 
5
21
  - Keep the responsive More-actions menu inside narrow viewports and above the editor canvas.
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)
@@ -88,10 +88,6 @@ export default App;
88
88
  | `media` | `boolean` | `true` | Enable/disable media/image functionality |
89
89
  | `formula` | `boolean` | `true` | Enable/disable formula/LaTeX functionality |
90
90
  | `mediaManager` | `MediaManagerAdapter` | `undefined` | Custom media manager for handling images |
91
- | `fonts` | `{ name: string; value: string }[]` | Default web-safe fonts | Custom font list for the toolbar |
92
- | `defaultFont` | `string` | `undefined` | Default font family for the editor content |
93
- | `theme` | `"light" \| "dark"` | `"light"` | Built-in theme mode |
94
- | `className` | `string` | `undefined` | Custom CSS class for theming via CSS variable overrides |
95
91
 
96
92
  ### Advanced Examples
97
93
 
@@ -318,87 +314,23 @@ const myMediaManager: MediaManagerAdapter = {
318
314
  };
319
315
  ```
320
316
 
321
- ## 🎨 Theming & Dark Mode
322
-
323
- The editor uses CSS custom properties (CSS variables) for all colors, making it fully customizable. No hardcoded colors — everything can be themed.
324
-
325
- ### Quick Start: Dark Mode
326
-
327
- ```tsx
328
- <ClassicEditor theme="dark" onChange={handleChange} />
329
- ```
330
-
331
- ### Custom Themes via CSS
332
-
333
- Apply a custom class and override any CSS variables:
334
-
335
- ```tsx
336
- <ClassicEditor className="my-theme" onChange={handleChange} />
337
- ```
338
-
339
- ```css
340
- .my-theme {
341
- --srte-bg: #1a1a2e;
342
- --srte-text: #eaeaea;
343
- --srte-border: #3a3a5c;
344
- /* Override only the variables you need */
345
- }
346
- ```
317
+ ## 🎨 Styling
347
318
 
348
- ### Responding to System Preference
319
+ The editor comes with built-in styles. You can customize the appearance by wrapping it in a container:
349
320
 
350
321
  ```tsx
351
- const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
352
-
353
- <ClassicEditor theme={prefersDark ? 'dark' : 'light'} />
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>
354
332
  ```
355
333
 
356
- Or purely via CSS (without the `theme` prop):
357
-
358
- ```css
359
- @media (prefers-color-scheme: dark) {
360
- .srte-editor {
361
- --srte-bg: #1e1e1e;
362
- --srte-text: #e0e0e0;
363
- --srte-border: #3a3a3a;
364
- /* ... */
365
- }
366
- }
367
- ```
368
-
369
- ### Available CSS Custom Properties
370
-
371
- | Variable | Default (Light) | Description |
372
- |---|---|---|
373
- | `--srte-bg` | `#ffffff` | Editor container & content background |
374
- | `--srte-text` | `#111111` | Primary UI text color |
375
- | `--srte-text-muted` | `#4b5563` | Secondary/subtle text |
376
- | `--srte-border` | `#dddddd` | Standard border color |
377
- | `--srte-border-light` | `#eeeeee` | Light separator borders |
378
- | `--srte-toolbar-bg` | `#ffffff` | Toolbar background |
379
- | `--srte-input-bg` | `#ffffff` | Button, input, select backgrounds |
380
- | `--srte-input-text` | `#111111` | Button, input, select text |
381
- | `--srte-input-border` | `#e5e7eb` | Button, input, select borders |
382
- | `--srte-modal-backdrop` | `rgba(0,0,0,0.35)` | Modal overlay background |
383
- | `--srte-modal-bg` | `#ffffff` | Modal/dialog background |
384
- | `--srte-modal-text` | `#000000` | Modal text color |
385
- | `--srte-menu-bg` | `#ffffff` | Context menu background |
386
- | `--srte-menu-text` | `#111111` | Context menu text |
387
- | `--srte-menu-shadow` | `0 8px 24px rgba(0,0,0,0.18)` | Menu box-shadow |
388
- | `--srte-accent` | `#1e90ff` | Accent/selection color |
389
- | `--srte-accent-bg` | `rgba(30,144,255,0.15)` | Accent background (translucent) |
390
- | `--srte-danger` | `#dc2626` | Destructive action color |
391
- | `--srte-primary` | `#2563eb` | Primary action button color |
392
- | `--srte-surface-subtle` | `#f3f4f6` | Subtle surface (dropzone, preset buttons) |
393
- | `--srte-on-primary` | `#ffffff` | Text on primary/danger buttons |
394
- | `--srte-cancel-bg` | `#f3f4f6` | Cancel button background |
395
-
396
- ### Important Notes
397
-
398
- - **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.
399
- - **Color picker swatches are not themed** — they display actual color values regardless of theme.
400
- - **The theme only affects editor chrome** — toolbar, dialogs, menus, and container. User content remains unchanged.
401
-
402
334
  ## 🛠️ Development
403
335
 
404
336
  ### Prerequisites
@@ -694,14 +626,13 @@ See the list of [contributors](https://github.com/ayush1852017/smart-rte/contrib
694
626
 
695
627
  ## 🗺️ Roadmap
696
628
 
697
- ### Current Version (0.2.x)
629
+ ### Current Version (0.1.x)
698
630
 
699
631
  - ✅ Rich text editing
700
632
  - ✅ Table support
701
633
  - ✅ Formula support (LaTeX/KaTeX)
702
634
  - ✅ Media management
703
635
  - ✅ TypeScript support
704
- - ✅ Dark mode & theming (CSS custom properties)
705
636
 
706
637
  ### Upcoming Features
707
638
 
@@ -0,0 +1,15 @@
1
+ import { inlineToolDeclarations, type Attrs, type SmartMark } from "smartrte-core";
2
+ export type CanonicalInlineToolId = typeof inlineToolDeclarations[number]["id"];
3
+ export type CanonicalInlineIntent = "apply" | "remove" | "toggle" | "setAttrs" | "clearAll" | "editLink";
4
+ export interface CanonicalInlineExecution {
5
+ readonly changed: boolean;
6
+ readonly operations: number;
7
+ readonly coverage: "all" | "partial" | "none";
8
+ }
9
+ export declare const executeCanonicalInlineTool: (root: HTMLElement, id: CanonicalInlineToolId, intent?: CanonicalInlineIntent, attrs?: Attrs) => CanonicalInlineExecution;
10
+ /** Consumes adapter stored marks for ordinary text input. Composition remains
11
+ * owned by the canonical surface; atom-aware composition is explicitly Phase 7. */
12
+ export declare const installCanonicalInlineStoredMarkInput: (root: HTMLElement) => (() => void);
13
+ export declare const canonicalInlineStoredMarks: (root: HTMLElement) => readonly SmartMark[];
14
+ export declare const hasCanonicalInlineStoredMarkOverride: (root: HTMLElement) => boolean;
15
+ export declare const describeCanonicalInlineCoverage: (root: HTMLElement) => Readonly<Record<string, "all" | "partial">>;
@@ -0,0 +1,269 @@
1
+ import { applyMarkCommand, applyOperations, clearAllMarksCommand, createFoundationEditor, createNodeId, createScopeIndex, editLinkCommand, executeMarkTool, foundationSchema, inlineToolDeclarations, parseCanonicalListHtml, removeLinkCommand, removeMarkCommand, serializeCanonicalListHtml, toggleMarkCommand, } from "smartrte-core";
2
+ const storedMarks = new WeakMap();
3
+ const ownerSelector = "p,h1,h2,h3,h4,h5,h6";
4
+ const ownerId = (owner) => {
5
+ var _a;
6
+ (_a = owner.dataset).smartId || (_a.smartId = createNodeId());
7
+ return owner.dataset.smartId;
8
+ };
9
+ const contentWidth = (node) => {
10
+ if (node.nodeType === node.TEXT_NODE)
11
+ return node.nodeValue?.length || 0;
12
+ if (!(node instanceof HTMLElement))
13
+ return [...node.childNodes].reduce((sum, child) => sum + contentWidth(child), 0);
14
+ if (node.closest("[data-smart-ui]"))
15
+ return 0;
16
+ if (node.matches("br,[data-smart-atomic='true']"))
17
+ return 1;
18
+ return [...node.childNodes].reduce((sum, child) => sum + contentWidth(child), 0);
19
+ };
20
+ const pointInOwner = (root, node, offset) => {
21
+ const element = node instanceof HTMLElement ? node : node?.parentElement;
22
+ const owner = element?.closest(ownerSelector);
23
+ if (!owner || !root.contains(owner) || !node)
24
+ return null;
25
+ const range = root.ownerDocument.createRange();
26
+ range.selectNodeContents(owner);
27
+ try {
28
+ range.setEnd(node, offset);
29
+ }
30
+ catch {
31
+ return null;
32
+ }
33
+ return { ownerId: ownerId(owner), offset: contentWidth(range.cloneContents()) };
34
+ };
35
+ const captureSelection = (root) => {
36
+ const native = root.ownerDocument.defaultView?.getSelection();
37
+ if (!native?.anchorNode || !native.focusNode)
38
+ return null;
39
+ const anchor = pointInOwner(root, native.anchorNode, native.anchorOffset);
40
+ const head = pointInOwner(root, native.focusNode, native.focusOffset);
41
+ return anchor && head ? { anchor, head } : null;
42
+ };
43
+ const domPointAt = (owner, requested) => {
44
+ let remaining = Math.max(0, requested);
45
+ const visit = (parent) => {
46
+ for (let index = 0; index < parent.childNodes.length; index += 1) {
47
+ const child = parent.childNodes[index];
48
+ if (child.nodeType === child.TEXT_NODE) {
49
+ const length = child.nodeValue?.length || 0;
50
+ if (remaining <= length)
51
+ return { node: child, offset: remaining };
52
+ remaining -= length;
53
+ }
54
+ else if (child instanceof HTMLElement && child.matches("br,[data-smart-atomic='true']")) {
55
+ if (remaining === 0)
56
+ return { node: parent, offset: index };
57
+ if (remaining === 1)
58
+ return { node: parent, offset: index + 1 };
59
+ remaining -= 1;
60
+ }
61
+ else if (!(child instanceof HTMLElement && child.closest("[data-smart-ui]"))) {
62
+ const found = visit(child);
63
+ if (found)
64
+ return found;
65
+ }
66
+ }
67
+ return null;
68
+ };
69
+ return visit(owner) || { node: owner, offset: owner.childNodes.length };
70
+ };
71
+ const restoreSelection = (root, snapshot) => {
72
+ const byId = (id) => Array.from(root.querySelectorAll("[data-smart-id]"))
73
+ .find((node) => node.matches(ownerSelector) && node.dataset.smartId === id) || null;
74
+ const anchorOwner = byId(snapshot.anchor.ownerId);
75
+ const headOwner = byId(snapshot.head.ownerId);
76
+ const native = root.ownerDocument.defaultView?.getSelection();
77
+ if (!anchorOwner || !headOwner || !native)
78
+ return;
79
+ const anchor = domPointAt(anchorOwner, snapshot.anchor.offset);
80
+ const head = domPointAt(headOwner, snapshot.head.offset);
81
+ native.setBaseAndExtent(anchor.node, anchor.offset, head.node, head.offset);
82
+ };
83
+ const selectedOwners = (root, snapshot) => {
84
+ const owners = Array.from(root.querySelectorAll(ownerSelector));
85
+ const indexes = [snapshot.anchor.ownerId, snapshot.head.ownerId].map((id) => owners.findIndex((owner) => ownerId(owner) === id));
86
+ if (indexes.some((index) => index < 0))
87
+ return [];
88
+ const [from, to] = [Math.min(...indexes), Math.max(...indexes)];
89
+ return owners.slice(from, to + 1);
90
+ };
91
+ const modelSelection = (document, snapshot) => {
92
+ const positions = createScopeIndex().positions(document, foundationSchema);
93
+ const point = (value) => {
94
+ const content = positions.contentRangeOf(value.ownerId);
95
+ return content ? { path: [...content.from.path], offset: value.offset } : null;
96
+ };
97
+ const anchor = point(snapshot.anchor);
98
+ const head = point(snapshot.head);
99
+ return anchor && head ? { type: "text", anchor, head } : null;
100
+ };
101
+ const parseOwners = (owners) => parseCanonicalListHtml(owners.map((owner) => owner.outerHTML).join(""));
102
+ const renderOwners = (root, owners, document, preserveEditedLink = false) => {
103
+ const template = root.ownerDocument.createElement("template");
104
+ template.innerHTML = serializeCanonicalListHtml(document, { fragment: true });
105
+ owners.forEach((owner) => {
106
+ const id = ownerId(owner);
107
+ const replacement = Array.from(template.content.querySelectorAll("[data-smart-id]"))
108
+ .find((candidate) => candidate.dataset.smartId === id);
109
+ if (replacement) {
110
+ const existingLinks = owner.querySelectorAll("a");
111
+ const replacementLinks = replacement.querySelectorAll("a");
112
+ if (preserveEditedLink && existingLinks.length === 1 && replacementLinks.length === 1
113
+ && existingLinks[0].textContent === replacementLinks[0].textContent
114
+ && owner.textContent === replacement.textContent) {
115
+ const next = replacementLinks[0];
116
+ existingLinks[0].setAttribute("href", next.getAttribute("href") || "");
117
+ if (next.target) {
118
+ existingLinks[0].target = next.target;
119
+ existingLinks[0].rel = "noopener noreferrer";
120
+ }
121
+ else {
122
+ existingLinks[0].removeAttribute("target");
123
+ existingLinks[0].removeAttribute("rel");
124
+ }
125
+ return;
126
+ }
127
+ owner.innerHTML = replacement.innerHTML;
128
+ owner.querySelectorAll("span[style]").forEach((span) => {
129
+ const color = span.style.color;
130
+ const background = span.style.backgroundColor;
131
+ const size = span.style.fontSize;
132
+ const family = span.style.fontFamily;
133
+ span.removeAttribute("style");
134
+ if (color)
135
+ span.style.color = color;
136
+ if (background)
137
+ span.style.backgroundColor = background;
138
+ if (size)
139
+ span.style.fontSize = size;
140
+ if (family)
141
+ span.style.fontFamily = family;
142
+ });
143
+ }
144
+ });
145
+ };
146
+ const declarationFor = (id) => {
147
+ const declaration = inlineToolDeclarations.find((tool) => tool.id === id);
148
+ if (!declaration)
149
+ throw new Error(`Unknown inline tool "${id}".`);
150
+ return declaration;
151
+ };
152
+ export const executeCanonicalInlineTool = (root, id, intent = "toggle", attrs) => {
153
+ const ownerElements = Array.from(root.querySelectorAll(ownerSelector));
154
+ const ownersWithPersistedIds = new Set(ownerElements.filter((owner) => owner.hasAttribute("data-smart-id")));
155
+ const cleanupTemporaryIds = () => ownerElements.forEach((owner) => {
156
+ if (!ownersWithPersistedIds.has(owner))
157
+ owner.removeAttribute("data-smart-id");
158
+ });
159
+ const snapshot = captureSelection(root);
160
+ if (!snapshot)
161
+ return { changed: false, operations: 0, coverage: "none" };
162
+ const owners = selectedOwners(root, snapshot);
163
+ if (!owners.length)
164
+ return { changed: false, operations: 0, coverage: "none" };
165
+ const document = parseOwners(owners);
166
+ const selection = modelSelection(document, snapshot);
167
+ if (!selection)
168
+ return { changed: false, operations: 0, coverage: "none" };
169
+ const declaration = declarationFor(id);
170
+ const index = createScopeIndex();
171
+ const scope = index.resolve(document, selection, { want: "inline-range" }, foundationSchema);
172
+ const description = index.resolve(document, selection, { want: "describe" }, foundationSchema);
173
+ const coverage = description.marks.some((entry) => entry.mark.type === declaration.markType && entry.coverage === "all")
174
+ ? "all" : description.marks.some((entry) => entry.mark.type === declaration.markType) ? "partial" : "none";
175
+ if (scope.collapsed && !(declaration.markType === "link" && (intent === "remove" || intent === "editLink"))) {
176
+ const editor = createFoundationEditor({ document, selection, storedMarks: storedMarks.get(root) });
177
+ executeMarkTool(editor, declaration, intent, attrs);
178
+ storedMarks.set(root, [...(editor.storedMarks || [])]);
179
+ cleanupTemporaryIds();
180
+ return { changed: false, operations: 0, coverage };
181
+ }
182
+ const ctx = { schema: foundationSchema, positions: index.positions(document, foundationSchema) };
183
+ const operations = intent === "clearAll" ? clearAllMarksCommand(document, scope, {}, ctx)
184
+ : intent === "remove" && declaration.markType === "link" ? removeLinkCommand(document, scope, { markType: "link" }, ctx)
185
+ : intent === "editLink" && declaration.markType === "link" ? editLinkCommand(document, scope, attrs, ctx)
186
+ : intent === "remove" ? removeMarkCommand(document, scope, { markType: declaration.markType }, ctx)
187
+ : intent === "toggle" ? toggleMarkCommand(document, scope, { markType: declaration.markType, attrs, coverage }, ctx)
188
+ : applyMarkCommand(document, scope, { markType: declaration.markType, attrs }, ctx);
189
+ if (!operations.length)
190
+ return { changed: false, operations: 0, coverage };
191
+ const output = applyOperations(document, operations);
192
+ renderOwners(root, owners, output, intent === "editLink");
193
+ restoreSelection(root, snapshot);
194
+ cleanupTemporaryIds();
195
+ storedMarks.delete(root);
196
+ return { changed: true, operations: operations.length, coverage };
197
+ };
198
+ /** Consumes adapter stored marks for ordinary text input. Composition remains
199
+ * owned by the canonical surface; atom-aware composition is explicitly Phase 7. */
200
+ export const installCanonicalInlineStoredMarkInput = (root) => {
201
+ const beforeInput = (event) => {
202
+ if (!storedMarks.has(root) || event.inputType !== "insertText" || !event.data)
203
+ return;
204
+ const marks = storedMarks.get(root) || [];
205
+ const ownerElements = Array.from(root.querySelectorAll(ownerSelector));
206
+ const ownersWithPersistedIds = new Set(ownerElements.filter((owner) => owner.hasAttribute("data-smart-id")));
207
+ const snapshot = captureSelection(root);
208
+ if (!snapshot || snapshot.anchor.ownerId !== snapshot.head.ownerId || snapshot.anchor.offset !== snapshot.head.offset)
209
+ return;
210
+ event.preventDefault();
211
+ const owners = selectedOwners(root, snapshot);
212
+ const document = parseOwners(owners);
213
+ const selection = modelSelection(document, snapshot);
214
+ if (!selection)
215
+ return;
216
+ const editor = createFoundationEditor({ document, selection, storedMarks: marks });
217
+ if (marks.length)
218
+ editor.typeText(event.data, { timestamp: Date.now() });
219
+ else
220
+ editor.transact((builder) => {
221
+ builder.operations.push({ type: "insertText", pos: selection.head, text: event.data });
222
+ const next = { path: [...selection.head.path], offset: selection.head.offset + event.data.length };
223
+ builder.setSelection({ type: "text", anchor: next, head: next });
224
+ builder.setStoredMarks([]);
225
+ }, { source: "input", timestamp: Date.now(), addToHistory: true });
226
+ renderOwners(root, owners, editor.document);
227
+ const next = { anchor: { ...snapshot.anchor, offset: snapshot.anchor.offset + event.data.length }, head: { ...snapshot.head, offset: snapshot.head.offset + event.data.length } };
228
+ restoreSelection(root, next);
229
+ ownerElements.forEach((owner) => { if (!ownersWithPersistedIds.has(owner))
230
+ owner.removeAttribute("data-smart-id"); });
231
+ storedMarks.set(root, [...(editor.storedMarks || [])]);
232
+ };
233
+ const selectionChange = () => {
234
+ const ownerElements = Array.from(root.querySelectorAll(ownerSelector));
235
+ const ownersWithPersistedIds = new Set(ownerElements.filter((owner) => owner.hasAttribute("data-smart-id")));
236
+ const snapshot = captureSelection(root);
237
+ if (!snapshot || snapshot.anchor.ownerId !== snapshot.head.ownerId || snapshot.anchor.offset !== snapshot.head.offset)
238
+ storedMarks.delete(root);
239
+ ownerElements.forEach((owner) => { if (!ownersWithPersistedIds.has(owner))
240
+ owner.removeAttribute("data-smart-id"); });
241
+ };
242
+ root.addEventListener("beforeinput", beforeInput);
243
+ root.ownerDocument.addEventListener("selectionchange", selectionChange);
244
+ return () => {
245
+ root.removeEventListener("beforeinput", beforeInput);
246
+ root.ownerDocument.removeEventListener("selectionchange", selectionChange);
247
+ storedMarks.delete(root);
248
+ };
249
+ };
250
+ export const canonicalInlineStoredMarks = (root) => storedMarks.get(root) || [];
251
+ export const hasCanonicalInlineStoredMarkOverride = (root) => storedMarks.has(root);
252
+ export const describeCanonicalInlineCoverage = (root) => {
253
+ if (storedMarks.has(root))
254
+ return Object.fromEntries((storedMarks.get(root) || []).map((mark) => [mark.type, "all"]));
255
+ const ownerElements = Array.from(root.querySelectorAll(ownerSelector));
256
+ const ownersWithPersistedIds = new Set(ownerElements.filter((owner) => owner.hasAttribute("data-smart-id")));
257
+ const snapshot = captureSelection(root);
258
+ if (!snapshot)
259
+ return {};
260
+ const owners = selectedOwners(root, snapshot);
261
+ const document = parseOwners(owners);
262
+ const selection = modelSelection(document, snapshot);
263
+ const description = selection
264
+ ? createScopeIndex().resolve(document, selection, { want: "describe" }, foundationSchema)
265
+ : null;
266
+ ownerElements.forEach((owner) => { if (!ownersWithPersistedIds.has(owner))
267
+ owner.removeAttribute("data-smart-id"); });
268
+ return Object.fromEntries((description?.marks || []).map((entry) => [entry.mark.type, entry.coverage]));
269
+ };
@@ -0,0 +1,26 @@
1
+ import { type SmartSelection } from "smartrte-core";
2
+ export declare const executeCanonicalListDepth: (root: HTMLElement, items: readonly HTMLElement[], direction: "indent" | "outdent") => boolean;
3
+ export declare const executeCanonicalListMove: (root: HTMLElement, items: readonly HTMLElement[], direction: "up" | "down") => boolean;
4
+ export declare const executeCanonicalListStyle: (args: {
5
+ root: HTMLElement;
6
+ items: readonly HTMLElement[];
7
+ style?: string;
8
+ preset?: string;
9
+ checkable?: boolean;
10
+ }) => boolean;
11
+ export declare const executeCanonicalListCheck: (root: HTMLElement, item: HTMLElement, checked: boolean) => boolean;
12
+ export declare const executeCanonicalListToggle: (args: {
13
+ root: HTMLElement;
14
+ items: readonly HTMLElement[];
15
+ blocks: readonly HTMLElement[];
16
+ listTag: "ul" | "ol";
17
+ range?: Range;
18
+ style?: string;
19
+ preset?: string;
20
+ checkable?: boolean;
21
+ }) => boolean;
22
+ export interface CanonicalListBridgeResult {
23
+ readonly changed: boolean;
24
+ readonly selection: SmartSelection | null;
25
+ }
26
+ export declare const executeCanonicalListStructuralInput: (root: HTMLElement, input: "enter" | "backspace" | "delete") => boolean;