smartrte-core 0.2.1 → 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 (329) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +23 -0
  3. package/dist/command.d.ts +8 -8
  4. package/dist/editor.d.ts +32 -0
  5. package/dist/editor.js +121 -0
  6. package/dist/foundation/annotations/index.d.ts +2 -0
  7. package/dist/foundation/annotations/index.js +2 -0
  8. package/dist/foundation/annotations/range.d.ts +54 -0
  9. package/dist/foundation/annotations/range.js +158 -0
  10. package/dist/foundation/annotations/types.d.ts +33 -0
  11. package/dist/foundation/annotations/types.js +1 -0
  12. package/dist/foundation/atom/commands.d.ts +11 -0
  13. package/dist/foundation/atom/commands.js +118 -0
  14. package/dist/foundation/atom/composition.d.ts +8 -0
  15. package/dist/foundation/atom/composition.js +22 -0
  16. package/dist/foundation/atom/declarations.d.ts +2 -0
  17. package/dist/foundation/atom/declarations.js +17 -0
  18. package/dist/foundation/atom/formats.d.ts +21 -0
  19. package/dist/foundation/atom/formats.js +77 -0
  20. package/dist/foundation/atom/index.d.ts +10 -0
  21. package/dist/foundation/atom/index.js +10 -0
  22. package/dist/foundation/atom/lifecycle.d.ts +12 -0
  23. package/dist/foundation/atom/lifecycle.js +41 -0
  24. package/dist/foundation/atom/persistence.d.ts +3 -0
  25. package/dist/foundation/atom/persistence.js +20 -0
  26. package/dist/foundation/atom/plugin.d.ts +20 -0
  27. package/dist/foundation/atom/plugin.js +55 -0
  28. package/dist/foundation/atom/schema.d.ts +3 -0
  29. package/dist/foundation/atom/schema.js +29 -0
  30. package/dist/foundation/atom/security.d.ts +10 -0
  31. package/dist/foundation/atom/security.js +20 -0
  32. package/dist/foundation/atom/selection.d.ts +3 -0
  33. package/dist/foundation/atom/selection.js +4 -0
  34. package/dist/foundation/atom/types.d.ts +47 -0
  35. package/dist/foundation/atom/types.js +1 -0
  36. package/dist/foundation/block/commands.d.ts +17 -0
  37. package/dist/foundation/block/commands.js +197 -0
  38. package/dist/foundation/block/formats.d.ts +33 -0
  39. package/dist/foundation/block/formats.js +57 -0
  40. package/dist/foundation/block/index.d.ts +5 -0
  41. package/dist/foundation/block/index.js +5 -0
  42. package/dist/foundation/block/input.d.ts +19 -0
  43. package/dist/foundation/block/input.js +67 -0
  44. package/dist/foundation/block/plugin.d.ts +26 -0
  45. package/dist/foundation/block/plugin.js +79 -0
  46. package/dist/foundation/block/schema.d.ts +49 -0
  47. package/dist/foundation/block/schema.js +27 -0
  48. package/dist/foundation/block/types.d.ts +40 -0
  49. package/dist/foundation/block/types.js +1 -0
  50. package/dist/foundation/boundaries.d.ts +11 -0
  51. package/dist/foundation/boundaries.js +117 -0
  52. package/dist/foundation/clipboard/detection.d.ts +3 -0
  53. package/dist/foundation/clipboard/detection.js +29 -0
  54. package/dist/foundation/clipboard/diagnostics.d.ts +24 -0
  55. package/dist/foundation/clipboard/diagnostics.js +59 -0
  56. package/dist/foundation/clipboard/index.d.ts +8 -0
  57. package/dist/foundation/clipboard/index.js +8 -0
  58. package/dist/foundation/clipboard/insertion.d.ts +22 -0
  59. package/dist/foundation/clipboard/insertion.js +210 -0
  60. package/dist/foundation/clipboard/normalizers.d.ts +8 -0
  61. package/dist/foundation/clipboard/normalizers.js +229 -0
  62. package/dist/foundation/clipboard/pipeline.d.ts +13 -0
  63. package/dist/foundation/clipboard/pipeline.js +58 -0
  64. package/dist/foundation/clipboard/sanitizer.d.ts +3 -0
  65. package/dist/foundation/clipboard/sanitizer.js +58 -0
  66. package/dist/foundation/clipboard/serialization.d.ts +5 -0
  67. package/dist/foundation/clipboard/serialization.js +33 -0
  68. package/dist/foundation/clipboard/transfer.d.ts +11 -0
  69. package/dist/foundation/clipboard/transfer.js +108 -0
  70. package/dist/foundation/clipboard/types.d.ts +65 -0
  71. package/dist/foundation/clipboard/types.js +1 -0
  72. package/dist/foundation/collab/index.d.ts +3 -0
  73. package/dist/foundation/collab/index.js +3 -0
  74. package/dist/foundation/collab/rebase.d.ts +28 -0
  75. package/dist/foundation/collab/rebase.js +82 -0
  76. package/dist/foundation/collab/transform.d.ts +73 -0
  77. package/dist/foundation/collab/transform.js +251 -0
  78. package/dist/foundation/collab/types.d.ts +52 -0
  79. package/dist/foundation/collab/types.js +1 -0
  80. package/dist/foundation/comments/fromSelection.d.ts +16 -0
  81. package/dist/foundation/comments/fromSelection.js +24 -0
  82. package/dist/foundation/comments/index.d.ts +3 -0
  83. package/dist/foundation/comments/index.js +3 -0
  84. package/dist/foundation/comments/rebase.d.ts +31 -0
  85. package/dist/foundation/comments/rebase.js +43 -0
  86. package/dist/foundation/comments/types.d.ts +37 -0
  87. package/dist/foundation/comments/types.js +1 -0
  88. package/dist/foundation/diff/documentDiff.d.ts +12 -0
  89. package/dist/foundation/diff/documentDiff.js +148 -0
  90. package/dist/foundation/diff/index.d.ts +3 -0
  91. package/dist/foundation/diff/index.js +3 -0
  92. package/dist/foundation/diff/textDiff.d.ts +12 -0
  93. package/dist/foundation/diff/textDiff.js +114 -0
  94. package/dist/foundation/diff/types.d.ts +74 -0
  95. package/dist/foundation/diff/types.js +1 -0
  96. package/dist/foundation/editor.d.ts +138 -0
  97. package/dist/foundation/editor.js +398 -0
  98. package/dist/foundation/formats/codec.d.ts +45 -0
  99. package/dist/foundation/formats/codec.js +1 -0
  100. package/dist/foundation/formats/docx/export.d.ts +4 -0
  101. package/dist/foundation/formats/docx/export.js +285 -0
  102. package/dist/foundation/formats/docx/import.d.ts +9 -0
  103. package/dist/foundation/formats/docx/import.js +37 -0
  104. package/dist/foundation/formats/docx/nestingGuard.d.ts +40 -0
  105. package/dist/foundation/formats/docx/nestingGuard.js +66 -0
  106. package/dist/foundation/formats/docx/portableAtoms.d.ts +4 -0
  107. package/dist/foundation/formats/docx/portableAtoms.js +66 -0
  108. package/dist/foundation/formats/docx/styledImport.d.ts +10 -0
  109. package/dist/foundation/formats/docx/styledImport.js +205 -0
  110. package/dist/foundation/formats/featureCodecs.d.ts +37 -0
  111. package/dist/foundation/formats/featureCodecs.js +69 -0
  112. package/dist/foundation/formats/fidelity.d.ts +31 -0
  113. package/dist/foundation/formats/fidelity.js +123 -0
  114. package/dist/foundation/formats/index.d.ts +7 -0
  115. package/dist/foundation/formats/index.js +7 -0
  116. package/dist/foundation/formats/pdf/format.d.ts +29 -0
  117. package/dist/foundation/formats/pdf/format.js +190 -0
  118. package/dist/foundation/history.d.ts +20 -0
  119. package/dist/foundation/history.js +121 -0
  120. package/dist/foundation/identity.d.ts +8 -0
  121. package/dist/foundation/identity.js +30 -0
  122. package/dist/foundation/index.d.ts +30 -0
  123. package/dist/foundation/index.js +30 -0
  124. package/dist/foundation/list/commands.d.ts +28 -0
  125. package/dist/foundation/list/commands.js +422 -0
  126. package/dist/foundation/list/formats.d.ts +20 -0
  127. package/dist/foundation/list/formats.js +795 -0
  128. package/dist/foundation/list/index.d.ts +8 -0
  129. package/dist/foundation/list/index.js +8 -0
  130. package/dist/foundation/list/input.d.ts +35 -0
  131. package/dist/foundation/list/input.js +320 -0
  132. package/dist/foundation/list/plugin.d.ts +21 -0
  133. package/dist/foundation/list/plugin.js +71 -0
  134. package/dist/foundation/list/presets.d.ts +16 -0
  135. package/dist/foundation/list/presets.js +21 -0
  136. package/dist/foundation/list/schema.d.ts +35 -0
  137. package/dist/foundation/list/schema.js +29 -0
  138. package/dist/foundation/list/shadow.d.ts +54 -0
  139. package/dist/foundation/list/shadow.js +75 -0
  140. package/dist/foundation/list/shadowCorpus.d.ts +10 -0
  141. package/dist/foundation/list/shadowCorpus.js +41 -0
  142. package/dist/foundation/list/types.d.ts +50 -0
  143. package/dist/foundation/list/types.js +1 -0
  144. package/dist/foundation/mapping.d.ts +11 -0
  145. package/dist/foundation/mapping.js +40 -0
  146. package/dist/foundation/marks/canonical.d.ts +9 -0
  147. package/dist/foundation/marks/canonical.js +72 -0
  148. package/dist/foundation/marks/commands.d.ts +21 -0
  149. package/dist/foundation/marks/commands.js +136 -0
  150. package/dist/foundation/marks/dom.d.ts +2 -0
  151. package/dist/foundation/marks/dom.js +46 -0
  152. package/dist/foundation/marks/editor.d.ts +8 -0
  153. package/dist/foundation/marks/editor.js +50 -0
  154. package/dist/foundation/marks/formats.d.ts +37 -0
  155. package/dist/foundation/marks/formats.js +49 -0
  156. package/dist/foundation/marks/hardBreak.d.ts +8 -0
  157. package/dist/foundation/marks/hardBreak.js +80 -0
  158. package/dist/foundation/marks/index.d.ts +11 -0
  159. package/dist/foundation/marks/index.js +11 -0
  160. package/dist/foundation/marks/normalization.d.ts +2 -0
  161. package/dist/foundation/marks/normalization.js +49 -0
  162. package/dist/foundation/marks/plugin.d.ts +14 -0
  163. package/dist/foundation/marks/plugin.js +63 -0
  164. package/dist/foundation/marks/resolveMarkRun.d.ts +8 -0
  165. package/dist/foundation/marks/resolveMarkRun.js +47 -0
  166. package/dist/foundation/marks/schema.d.ts +167 -0
  167. package/dist/foundation/marks/schema.js +50 -0
  168. package/dist/foundation/marks/stored.d.ts +2 -0
  169. package/dist/foundation/marks/stored.js +31 -0
  170. package/dist/foundation/marks/types.d.ts +40 -0
  171. package/dist/foundation/marks/types.js +1 -0
  172. package/dist/foundation/modelDom.d.ts +38 -0
  173. package/dist/foundation/modelDom.js +327 -0
  174. package/dist/foundation/normalization.d.ts +22 -0
  175. package/dist/foundation/normalization.js +132 -0
  176. package/dist/foundation/operations.d.ts +28 -0
  177. package/dist/foundation/operations.js +568 -0
  178. package/dist/foundation/plugin/builtins.d.ts +10 -0
  179. package/dist/foundation/plugin/builtins.js +27 -0
  180. package/dist/foundation/plugin/dispatch.d.ts +33 -0
  181. package/dist/foundation/plugin/dispatch.js +34 -0
  182. package/dist/foundation/plugin/index.d.ts +4 -0
  183. package/dist/foundation/plugin/index.js +4 -0
  184. package/dist/foundation/plugin/registry.d.ts +36 -0
  185. package/dist/foundation/plugin/registry.js +115 -0
  186. package/dist/foundation/plugin/types.d.ts +141 -0
  187. package/dist/foundation/plugin/types.js +13 -0
  188. package/dist/foundation/positions.d.ts +20 -0
  189. package/dist/foundation/positions.js +121 -0
  190. package/dist/foundation/schema.d.ts +57 -0
  191. package/dist/foundation/schema.js +371 -0
  192. package/dist/foundation/schemaBuilder.d.ts +41 -0
  193. package/dist/foundation/schemaBuilder.js +96 -0
  194. package/dist/foundation/scope/index.d.ts +2 -0
  195. package/dist/foundation/scope/index.js +2 -0
  196. package/dist/foundation/scope/resolveScope.d.ts +13 -0
  197. package/dist/foundation/scope/resolveScope.js +860 -0
  198. package/dist/foundation/scope/types.d.ts +122 -0
  199. package/dist/foundation/scope/types.js +1 -0
  200. package/dist/foundation/security/urlPolicy.d.ts +21 -0
  201. package/dist/foundation/security/urlPolicy.js +75 -0
  202. package/dist/foundation/structural/contentTarget.d.ts +21 -0
  203. package/dist/foundation/structural/contentTarget.js +51 -0
  204. package/dist/foundation/structural/index.d.ts +2 -0
  205. package/dist/foundation/structural/index.js +2 -0
  206. package/dist/foundation/structural/move.d.ts +7 -0
  207. package/dist/foundation/structural/move.js +35 -0
  208. package/dist/foundation/suggestions/index.d.ts +3 -0
  209. package/dist/foundation/suggestions/index.js +3 -0
  210. package/dist/foundation/suggestions/inline.d.ts +64 -0
  211. package/dist/foundation/suggestions/inline.js +116 -0
  212. package/dist/foundation/suggestions/structural.d.ts +40 -0
  213. package/dist/foundation/suggestions/structural.js +47 -0
  214. package/dist/foundation/suggestions/types.d.ts +19 -0
  215. package/dist/foundation/suggestions/types.js +1 -0
  216. package/dist/foundation/surface/index.d.ts +3 -0
  217. package/dist/foundation/surface/index.js +3 -0
  218. package/dist/foundation/surface/input.d.ts +127 -0
  219. package/dist/foundation/surface/input.js +1761 -0
  220. package/dist/foundation/surface/renderer.d.ts +84 -0
  221. package/dist/foundation/surface/renderer.js +974 -0
  222. package/dist/foundation/surface/types.d.ts +48 -0
  223. package/dist/foundation/surface/types.js +1 -0
  224. package/dist/foundation/table/commands.d.ts +31 -0
  225. package/dist/foundation/table/commands.js +470 -0
  226. package/dist/foundation/table/formats.d.ts +26 -0
  227. package/dist/foundation/table/formats.js +40 -0
  228. package/dist/foundation/table/grid.d.ts +8 -0
  229. package/dist/foundation/table/grid.js +178 -0
  230. package/dist/foundation/table/index.d.ts +6 -0
  231. package/dist/foundation/table/index.js +6 -0
  232. package/dist/foundation/table/plugin.d.ts +23 -0
  233. package/dist/foundation/table/plugin.js +119 -0
  234. package/dist/foundation/table/schema.d.ts +2 -0
  235. package/dist/foundation/table/schema.js +38 -0
  236. package/dist/foundation/table/selection.d.ts +7 -0
  237. package/dist/foundation/table/selection.js +35 -0
  238. package/dist/foundation/table/types.d.ts +109 -0
  239. package/dist/foundation/table/types.js +1 -0
  240. package/dist/foundation/transactions.d.ts +41 -0
  241. package/dist/foundation/transactions.js +90 -0
  242. package/dist/foundation/types.d.ts +261 -0
  243. package/dist/foundation/types.js +1 -0
  244. package/dist/foundation/versioning/diffVersions.d.ts +5 -0
  245. package/dist/foundation/versioning/diffVersions.js +3 -0
  246. package/dist/foundation/versioning/index.d.ts +2 -0
  247. package/dist/foundation/versioning/index.js +2 -0
  248. package/dist/foundation/versioning/types.d.ts +17 -0
  249. package/dist/foundation/versioning/types.js +1 -0
  250. package/dist/history.d.ts +20 -0
  251. package/dist/history.js +165 -0
  252. package/dist/html/compatibility.js +7 -1
  253. package/dist/index.d.ts +1 -14
  254. package/dist/index.js +9 -14
  255. package/dist/legacy/index.d.ts +45 -0
  256. package/dist/legacy/index.js +45 -0
  257. package/dist/legacyCommands/alignment.d.ts +20 -0
  258. package/dist/{commands → legacyCommands}/alignment.js +17 -0
  259. package/dist/legacyCommands/blocks.d.ts +14 -0
  260. package/dist/legacyCommands/blocks.js +105 -0
  261. package/dist/legacyCommands/checklist.d.ts +11 -0
  262. package/dist/legacyCommands/checklist.js +45 -0
  263. package/dist/legacyCommands/code.d.ts +15 -0
  264. package/dist/{commands → legacyCommands}/code.js +25 -1
  265. package/dist/legacyCommands/formula.d.ts +7 -0
  266. package/dist/legacyCommands/formula.js +67 -0
  267. package/dist/legacyCommands/inlineAtoms.d.ts +7 -0
  268. package/dist/legacyCommands/inlineAtoms.js +58 -0
  269. package/dist/legacyCommands/list.d.ts +38 -0
  270. package/dist/legacyCommands/list.js +477 -0
  271. package/dist/{commands → legacyCommands}/marks.d.ts +3 -0
  272. package/dist/{commands → legacyCommands}/marks.js +28 -11
  273. package/dist/legacyCommands/media.d.ts +31 -0
  274. package/dist/legacyCommands/media.js +205 -0
  275. package/dist/legacyCommands/move.d.ts +14 -0
  276. package/dist/legacyCommands/move.js +91 -0
  277. package/dist/legacyCommands/table.d.ts +91 -0
  278. package/dist/legacyCommands/table.js +547 -0
  279. package/dist/listPresets.d.ts +12 -0
  280. package/dist/listPresets.js +6 -0
  281. package/dist/listScope.d.ts +27 -0
  282. package/dist/listScope.js +104 -0
  283. package/dist/markdown/compatibility.d.ts +2 -0
  284. package/dist/markdown/compatibility.js +94 -2
  285. package/dist/marks.d.ts +5 -5
  286. package/dist/model.d.ts +65 -9
  287. package/dist/plugin.d.ts +15 -0
  288. package/dist/plugin.js +31 -0
  289. package/dist/plugins/alignment.d.ts +3 -0
  290. package/dist/plugins/alignment.js +6 -0
  291. package/dist/plugins/basicFormatting.d.ts +9 -0
  292. package/dist/plugins/basicFormatting.js +24 -0
  293. package/dist/plugins/blockType.d.ts +3 -0
  294. package/dist/plugins/blockType.js +6 -0
  295. package/dist/plugins/blockquote.d.ts +3 -0
  296. package/dist/plugins/blockquote.js +6 -0
  297. package/dist/plugins/checklist.d.ts +3 -0
  298. package/dist/plugins/checklist.js +10 -0
  299. package/dist/plugins/codeBlock.d.ts +3 -0
  300. package/dist/plugins/codeBlock.js +6 -0
  301. package/dist/plugins/formula.d.ts +3 -0
  302. package/dist/plugins/formula.js +9 -0
  303. package/dist/plugins/list.d.ts +3 -0
  304. package/dist/plugins/list.js +10 -0
  305. package/dist/plugins/media.d.ts +3 -0
  306. package/dist/plugins/media.js +12 -0
  307. package/dist/plugins/move.d.ts +3 -0
  308. package/dist/plugins/move.js +9 -0
  309. package/dist/plugins/table.d.ts +3 -0
  310. package/dist/plugins/table.js +22 -0
  311. package/dist/preset.d.ts +12 -0
  312. package/dist/preset.js +40 -0
  313. package/dist/schema.d.ts +22 -0
  314. package/dist/schema.js +333 -0
  315. package/dist/security/urlPolicy.d.ts +1 -17
  316. package/dist/security/urlPolicy.js +3 -69
  317. package/dist/selection.d.ts +1 -1
  318. package/dist/selectionMapping.d.ts +4 -0
  319. package/dist/selectionMapping.js +222 -0
  320. package/dist/table.d.ts +3 -3
  321. package/dist/transaction.d.ts +35 -12
  322. package/dist/transaction.js +174 -28
  323. package/dist/tree.d.ts +8 -2
  324. package/dist/tree.js +44 -0
  325. package/package.json +27 -8
  326. package/dist/commands/alignment.d.ts +0 -18
  327. package/dist/commands/code.d.ts +0 -13
  328. package/dist/commands/list.d.ts +0 -20
  329. package/dist/commands/list.js +0 -52
@@ -0,0 +1,122 @@
1
+ import type { ResolvedPos, SmartDocument, SmartMark, SmartNode, SmartRange, SmartSchema, SmartSelection } from "../types.js";
2
+ export type ScopeRequest = {
3
+ want: "inline-range";
4
+ } | {
5
+ want: "block-range";
6
+ } | {
7
+ want: "container-tree";
8
+ stopAt?: (node: SmartNode) => boolean;
9
+ } | {
10
+ want: "list-selection";
11
+ } | {
12
+ want: "table-grid";
13
+ } | {
14
+ want: "atomic-node";
15
+ } | {
16
+ want: "describe";
17
+ };
18
+ export type ScopeKind = "inline-range" | "block-range" | "container-tree" | "list-selection" | "table-grid" | "atomic-node" | "mixed" | "empty";
19
+ export interface ScopeBase {
20
+ kind: ScopeKind;
21
+ /** Normalized, direction-independent. Always from <= to. */
22
+ range: SmartRange;
23
+ /** Nearest isolating ancestor fully containing this scope, if any. */
24
+ isolatingAncestorId: string | null;
25
+ /** True when the request could not be satisfied exactly and was clamped. */
26
+ clamped: boolean;
27
+ /** Why it was clamped — isolating boundary, atom boundary, document edge. */
28
+ clampReason?: "isolating" | "atomic" | "document-edge" | "schema";
29
+ }
30
+ export interface InlineRun {
31
+ ownerNodeId: string;
32
+ from: number;
33
+ to: number;
34
+ containsAtoms: boolean;
35
+ }
36
+ export interface InlineRangeScope extends ScopeBase {
37
+ kind: "inline-range";
38
+ runs: InlineRun[];
39
+ collapsed: boolean;
40
+ storedMarkAnchor?: ResolvedPos;
41
+ }
42
+ export interface BlockRangeScope extends ScopeBase {
43
+ kind: "block-range";
44
+ blockIds: string[];
45
+ promotedFromPartial: boolean;
46
+ commonParentId: string | null;
47
+ }
48
+ export interface ContainerTreeScope extends ScopeBase {
49
+ kind: "container-tree";
50
+ rootId: string;
51
+ nodeIds: string[];
52
+ promotedFromPartial: boolean;
53
+ }
54
+ export interface ListSelectionScope extends ScopeBase {
55
+ kind: "list-selection";
56
+ listId: string;
57
+ items: {
58
+ itemId: string;
59
+ depth: number;
60
+ hasChildList: boolean;
61
+ }[];
62
+ partialSubtree: boolean;
63
+ promotedFromPartial: boolean;
64
+ }
65
+ export interface TableGridScope extends ScopeBase {
66
+ kind: "table-grid";
67
+ tableId: string;
68
+ rect: {
69
+ top: number;
70
+ left: number;
71
+ bottom: number;
72
+ right: number;
73
+ };
74
+ cellIds: string[];
75
+ coveredCellIds: string[];
76
+ rectangular: boolean;
77
+ }
78
+ export interface AtomicNodeScope extends ScopeBase {
79
+ kind: "atomic-node";
80
+ nodeId: string;
81
+ inline: boolean;
82
+ }
83
+ export interface MixedScope extends ScopeBase {
84
+ kind: "mixed";
85
+ parts: ResolvedScope[];
86
+ }
87
+ export interface EmptyScope extends ScopeBase {
88
+ kind: "empty";
89
+ }
90
+ export type ResolvedScope = InlineRangeScope | BlockRangeScope | ContainerTreeScope | ListSelectionScope | TableGridScope | AtomicNodeScope | MixedScope | EmptyScope;
91
+ export interface SelectionDescription {
92
+ blockTypes: string[];
93
+ marks: {
94
+ mark: SmartMark;
95
+ coverage: "all" | "partial";
96
+ }[];
97
+ inList: {
98
+ listId: string;
99
+ depth: number;
100
+ } | null;
101
+ inTable: {
102
+ tableId: string;
103
+ cellId: string;
104
+ } | null;
105
+ isolatingAncestorId: string | null;
106
+ atoms: string[];
107
+ collapsed: boolean;
108
+ spansIsolatingBoundary: boolean;
109
+ }
110
+ export type ScopeResult = ResolvedScope | SelectionDescription;
111
+ export interface PositionLookup {
112
+ positionOf(nodeId: string): ResolvedPos | null;
113
+ rangeOf(nodeId: string): SmartRange | null;
114
+ contentRangeOf(nodeId: string): SmartRange | null;
115
+ exists(nodeId: string): boolean;
116
+ }
117
+ export interface ScopeIndex {
118
+ resolve(document: SmartDocument, selection: SmartSelection, request: ScopeRequest, schema: SmartSchema): ScopeResult;
119
+ positions(document: SmartDocument, schema: SmartSchema): PositionLookup;
120
+ readonly liveNodeCount: number;
121
+ }
122
+ export type ResolveScope = (document: SmartDocument, selection: SmartSelection, request: ScopeRequest, schema: SmartSchema) => ScopeResult;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ export interface SafeLinkAttrs {
2
+ href: string;
3
+ target?: string;
4
+ }
5
+ export type NormalizedLinkKind = "url" | "email" | "phone" | "anchor" | "invalid";
6
+ export interface NormalizedLinkResult {
7
+ kind: NormalizedLinkKind;
8
+ href: string | null;
9
+ input: string;
10
+ }
11
+ export declare const normalizeLinkInput: (input: string | undefined | null) => NormalizedLinkResult;
12
+ export declare const sanitizeLinkHref: (href: string | undefined | null) => string | null;
13
+ export declare const sanitizeLinkTarget: (target: string | undefined) => string | undefined;
14
+ export declare const sanitizeLinkAttrs: (input: {
15
+ href?: string;
16
+ target?: string;
17
+ }) => SafeLinkAttrs | null;
18
+ export declare const sanitizeResourceUrl: (input: string | undefined | null, options?: {
19
+ allowBlob?: boolean;
20
+ allowedDataMimeTypes?: ReadonlySet<string>;
21
+ }) => string | null;
@@ -0,0 +1,75 @@
1
+ const allowedProtocols = new Set(["http:", "https:", "mailto:", "tel:"]);
2
+ const resourceProtocols = new Set(["http:", "https:"]);
3
+ const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
4
+ const bareDomainPattern = /^(?:www\.)?[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+(?:[/?#][^\s]*)?$/;
5
+ const phonePattern = /^\+?[\d\s().-]{7,}$/;
6
+ const normalizePhone = (value) => {
7
+ const hasLeadingPlus = value.trim().startsWith("+");
8
+ const digits = value.replace(/[^\d]/g, "");
9
+ if (digits.length < 7)
10
+ return null;
11
+ return `${hasLeadingPlus ? "+" : ""}${digits}`;
12
+ };
13
+ export const normalizeLinkInput = (input) => {
14
+ const trimmed = input?.trim() || "";
15
+ if (!trimmed)
16
+ return { kind: "invalid", href: null, input: "" };
17
+ if (/^mailto:/i.test(trimmed)) {
18
+ const email = trimmed.slice("mailto:".length).trim();
19
+ const href = emailPattern.test(email) ? `mailto:${email}` : null;
20
+ return { kind: href ? "email" : "invalid", href, input: trimmed };
21
+ }
22
+ if (/^tel:/i.test(trimmed)) {
23
+ const phone = normalizePhone(trimmed.slice("tel:".length));
24
+ const href = phone ? `tel:${phone}` : null;
25
+ return { kind: href ? "phone" : "invalid", href, input: trimmed };
26
+ }
27
+ if (emailPattern.test(trimmed))
28
+ return { kind: "email", href: `mailto:${trimmed}`, input: trimmed };
29
+ const phone = normalizePhone(trimmed);
30
+ if (phone && (trimmed.startsWith("+") || phonePattern.test(trimmed))) {
31
+ return { kind: "phone", href: `tel:${phone}`, input: trimmed };
32
+ }
33
+ if (/^#[A-Za-z][\w:.-]*$/.test(trimmed))
34
+ return { kind: "anchor", href: trimmed, input: trimmed };
35
+ if (bareDomainPattern.test(trimmed))
36
+ return { kind: "url", href: `https://${trimmed}`, input: trimmed };
37
+ try {
38
+ const parsed = new URL(trimmed);
39
+ const href = allowedProtocols.has(parsed.protocol) ? trimmed : null;
40
+ return { kind: href ? "url" : "invalid", href, input: trimmed };
41
+ }
42
+ catch {
43
+ return { kind: "invalid", href: null, input: trimmed };
44
+ }
45
+ };
46
+ export const sanitizeLinkHref = (href) => normalizeLinkInput(href).href;
47
+ export const sanitizeLinkTarget = (target) => target?.trim() || undefined;
48
+ export const sanitizeLinkAttrs = (input) => {
49
+ const href = sanitizeLinkHref(input.href);
50
+ return href ? { href, target: sanitizeLinkTarget(input.target) } : null;
51
+ };
52
+ export const sanitizeResourceUrl = (input, options = {}) => {
53
+ const value = input?.trim() || "";
54
+ if (!value || /[\u0000-\u001f\u007f]/.test(value))
55
+ return null;
56
+ if (/^(?:\/[^/]|\.\.?\/)/.test(value))
57
+ return value;
58
+ // A path-relative resource such as `images/photo.png` is valid persisted
59
+ // content. Reject anything that looks like a scheme so executable protocols
60
+ // cannot disguise themselves as relative paths.
61
+ if (!/^[A-Za-z][A-Za-z0-9+.-]*:/.test(value) && !value.startsWith("//") && !/\s/.test(value))
62
+ return value;
63
+ if (options.allowBlob && /^blob:/i.test(value))
64
+ return value;
65
+ const data = value.match(/^data:([^;,]+)(?:;[^,]*)?,/i);
66
+ if (data)
67
+ return options.allowedDataMimeTypes?.has(data[1].toLowerCase()) ? value : null;
68
+ try {
69
+ const parsed = new URL(value);
70
+ return resourceProtocols.has(parsed.protocol) ? value : null;
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ };
@@ -0,0 +1,21 @@
1
+ import type { PositionLookup } from "../scope/types.js";
2
+ import type { SmartDocument, SmartSchema } from "../types.js";
3
+ export interface ContentTargetContext {
4
+ readonly schema: SmartSchema;
5
+ readonly positions: PositionLookup;
6
+ }
7
+ export interface ContentTargetLineageEntry {
8
+ readonly nodeId: string;
9
+ readonly type: string;
10
+ }
11
+ export interface ResolvedContentTarget {
12
+ readonly ownerId: string;
13
+ /** Target-subtree lineage, outermost first and including the owner. */
14
+ readonly lineage: readonly ContentTargetLineageEntry[];
15
+ }
16
+ /**
17
+ * Resolves the previous visible content owner in logical document order.
18
+ * It descends through the deepest last sibling subtree, climbs across parent
19
+ * boundaries when needed, and never crosses the nearest isolating ancestor.
20
+ */
21
+ export declare const resolvePrecedingContentTarget: (document: SmartDocument, nodeId: string, ctx: ContentTargetContext) => ResolvedContentTarget | null;
@@ -0,0 +1,51 @@
1
+ import { isTextNode } from "../identity.js";
2
+ import { nodeAtPath } from "../positions.js";
3
+ const isContentOwner = (node, schema) => {
4
+ const expression = schema.nodes[node.type]?.content || "";
5
+ return expression.includes("inline") || expression.includes("text");
6
+ };
7
+ const deepestLastContentOwner = (node, schema, lineage = []) => {
8
+ const nextLineage = [...lineage, { nodeId: node.id, type: node.type }];
9
+ for (let index = (node.children?.length || 0) - 1; index >= 0; index -= 1) {
10
+ const child = node.children?.[index];
11
+ if (!child || isTextNode(child))
12
+ continue;
13
+ const target = deepestLastContentOwner(child, schema, nextLineage);
14
+ if (target)
15
+ return target;
16
+ }
17
+ return isContentOwner(node, schema) ? { ownerId: node.id, lineage: nextLineage } : null;
18
+ };
19
+ const nearestIsolatingPath = (document, path, schema) => {
20
+ let result = [];
21
+ for (let depth = 0; depth <= path.length; depth += 1) {
22
+ const candidate = path.slice(0, depth);
23
+ const node = nodeAtPath(document, candidate);
24
+ if (node && !isTextNode(node) && schema.nodes[node.type]?.isolating)
25
+ result = candidate;
26
+ }
27
+ return result;
28
+ };
29
+ /**
30
+ * Resolves the previous visible content owner in logical document order.
31
+ * It descends through the deepest last sibling subtree, climbs across parent
32
+ * boundaries when needed, and never crosses the nearest isolating ancestor.
33
+ */
34
+ export const resolvePrecedingContentTarget = (document, nodeId, ctx) => {
35
+ const located = ctx.positions.positionOf(nodeId);
36
+ if (!located)
37
+ return null;
38
+ let path = [...located.pos.path, located.pos.offset];
39
+ const isolationPath = nearestIsolatingPath(document, path, ctx.schema);
40
+ while (path.length > isolationPath.length) {
41
+ const parentPath = path.slice(0, -1);
42
+ const index = path[path.length - 1];
43
+ if (index > 0) {
44
+ const sibling = nodeAtPath(document, [...parentPath, index - 1]);
45
+ if (sibling && !isTextNode(sibling))
46
+ return deepestLastContentOwner(sibling, ctx.schema);
47
+ }
48
+ path = parentPath;
49
+ }
50
+ return null;
51
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./contentTarget.js";
2
+ export * from "./move.js";
@@ -0,0 +1,2 @@
1
+ export * from "./contentTarget.js";
2
+ export * from "./move.js";
@@ -0,0 +1,7 @@
1
+ import type { PositionLookup } from "../scope/types.js";
2
+ import type { SmartOperation } from "../types.js";
3
+ export interface SiblingMoveContext {
4
+ readonly positions: PositionLookup;
5
+ }
6
+ /** Shared implementation used by block.move and list.move. */
7
+ export declare const moveContiguousSiblings: (nodeIds: readonly string[], direction: "up" | "down", ctx: SiblingMoveContext) => SmartOperation[];
@@ -0,0 +1,35 @@
1
+ import { isTextNode } from "../identity.js";
2
+ /** Shared implementation used by block.move and list.move. */
3
+ export const moveContiguousSiblings = (nodeIds, direction, ctx) => {
4
+ const located = [...new Set(nodeIds)].map((nodeId) => {
5
+ const resolved = ctx.positions.positionOf(nodeId);
6
+ const node = resolved?.parent.children?.[resolved.pos.offset];
7
+ if (!resolved || !node || isTextNode(node) || node.id !== nodeId)
8
+ return null;
9
+ return { nodeId, parentId: resolved.parent.id, path: resolved.pos.path, index: resolved.pos.offset };
10
+ });
11
+ if (located.some((entry) => !entry))
12
+ return [];
13
+ const entries = located.filter((entry) => Boolean(entry))
14
+ .sort((left, right) => left.index - right.index);
15
+ if (!entries.length || entries.some((entry) => entry.parentId !== entries[0].parentId)
16
+ || entries.some((entry, index) => index > 0 && entry.index !== entries[index - 1].index + 1))
17
+ return [];
18
+ const first = entries[0].index;
19
+ const last = entries[entries.length - 1].index;
20
+ const parent = ctx.positions.positionOf(entries[0].nodeId).parent;
21
+ if (direction === "up") {
22
+ if (first === 0)
23
+ return [];
24
+ const preceding = parent.children?.[first - 1];
25
+ if (!preceding || isTextNode(preceding))
26
+ return [];
27
+ return [{ type: "moveNode", from: { path: [...entries[0].path], offset: first - 1 }, to: { path: [...entries[0].path], offset: last }, nodeId: preceding.id }];
28
+ }
29
+ if (last >= (parent.children?.length || 0) - 1)
30
+ return [];
31
+ const following = parent.children?.[last + 1];
32
+ if (!following || isTextNode(following))
33
+ return [];
34
+ return [{ type: "moveNode", from: { path: [...entries[0].path], offset: last + 1 }, to: { path: [...entries[0].path], offset: first }, nodeId: following.id }];
35
+ };
@@ -0,0 +1,3 @@
1
+ export * from "./types.js";
2
+ export * from "./inline.js";
3
+ export * from "./structural.js";
@@ -0,0 +1,3 @@
1
+ export * from "./types.js";
2
+ export * from "./inline.js";
3
+ export * from "./structural.js";
@@ -0,0 +1,64 @@
1
+ import type { MarkCommandContext } from "../marks/types.js";
2
+ import type { ResolvedScope } from "../scope/types.js";
3
+ import type { SmartDocument, SmartMark, SmartOperation, SmartPos, SmartRange } from "../types.js";
4
+ import type { SuggestionKind } from "./types.js";
5
+ export interface SuggestSpanParams {
6
+ readonly authorId: string;
7
+ readonly id?: string;
8
+ readonly createdAt?: number;
9
+ }
10
+ /**
11
+ * Builds a single insertText operation carrying a fresh "insert"-kind
12
+ * suggestion mark alongside whatever other marks the caller wants (e.g.
13
+ * storedMarks/bold). The text is inserted for real - track-changes mode
14
+ * means the document contains the real, live content immediately, tagged
15
+ * for later accept/reject, not a deferred change.
16
+ */
17
+ export declare const suggestInsertOperation: (pos: SmartPos, text: string, params: SuggestSpanParams, extraMarks?: readonly SmartMark[]) => SmartOperation;
18
+ /**
19
+ * Marks a selection's content with a "delete"-kind suggestion instead of
20
+ * actually deleting it - the content stays in the document (rendered
21
+ * struck-through) until the suggestion is accepted. Reuses
22
+ * applyMarkCommand directly: a suggestion mark is, mechanically, just
23
+ * another mark, so the existing per-owner range splitting/exclusion
24
+ * machinery already does exactly what's needed for a selection spanning
25
+ * multiple paragraphs.
26
+ */
27
+ export declare const suggestDeleteCommand: (document: SmartDocument, scope: ResolvedScope, params: SuggestSpanParams, ctx: MarkCommandContext) => SmartOperation[];
28
+ /**
29
+ * The single-range counterpart to suggestDeleteCommand, for a caller that
30
+ * already has a concrete single-owner SmartRange in hand (e.g. the input
31
+ * pipeline's ambient track-changes mode) and has no ResolvedScope to
32
+ * resolve - one addMark operation, no scope/positions machinery involved.
33
+ */
34
+ export declare const suggestDeleteRangeOperation: (range: SmartRange, params: SuggestSpanParams) => SmartOperation;
35
+ export interface SuggestionRun {
36
+ readonly ownerNodeId: string;
37
+ readonly range: SmartRange;
38
+ readonly mark: SmartMark;
39
+ readonly text: string;
40
+ }
41
+ /**
42
+ * Finds every contiguous run of text carrying a suggestion mark with the
43
+ * given id, anywhere in the document - not scoped to a caret position
44
+ * (unlike resolveMarkRun), since accept/reject is driven by a UI marker
45
+ * click, not necessarily a caret inside the run. A suggestion can in
46
+ * principle be split across owners by an intervening structural edit
47
+ * (rare), so this returns one entry per owner it's found in, not
48
+ * assuming a single contiguous span.
49
+ */
50
+ export declare const findSuggestionRuns: (document: SmartDocument, id: string) => SuggestionRun[];
51
+ /** Accept: "insert" runs keep their text (mark stripped); "delete" runs are actually removed. */
52
+ export declare const acceptSuggestionCommand: (document: SmartDocument, id: string) => SmartOperation[];
53
+ /** Reject: "insert" runs are removed (the proposed addition never happened); "delete" runs keep their text (mark stripped). */
54
+ export declare const rejectSuggestionCommand: (document: SmartDocument, id: string) => SmartOperation[];
55
+ export interface InlineSuggestionSummary {
56
+ readonly id: string;
57
+ readonly authorId: string;
58
+ readonly kind: SuggestionKind;
59
+ readonly createdAt: number;
60
+ /** Concatenation of every run's text sharing this id, in document order - a display preview, not authoritative content. */
61
+ readonly text: string;
62
+ }
63
+ /** One entry per distinct suggestion id found anywhere in the document - the panel-listing counterpart to findSuggestionRuns' by-id lookup. */
64
+ export declare const listInlineSuggestions: (document: SmartDocument) => InlineSuggestionSummary[];
@@ -0,0 +1,116 @@
1
+ import { createNodeId, isTextNode } from "../identity.js";
2
+ import { comparePos } from "../positions.js";
3
+ import { applyMarkCommand } from "../marks/commands.js";
4
+ const suggestionMark = (kind, params) => ({
5
+ type: "suggestion",
6
+ attrs: { id: params.id || createNodeId(), authorId: params.authorId, kind, createdAt: params.createdAt ?? Date.now() },
7
+ });
8
+ /**
9
+ * Builds a single insertText operation carrying a fresh "insert"-kind
10
+ * suggestion mark alongside whatever other marks the caller wants (e.g.
11
+ * storedMarks/bold). The text is inserted for real - track-changes mode
12
+ * means the document contains the real, live content immediately, tagged
13
+ * for later accept/reject, not a deferred change.
14
+ */
15
+ export const suggestInsertOperation = (pos, text, params, extraMarks = []) => ({
16
+ type: "insertText",
17
+ pos,
18
+ text,
19
+ marks: [...extraMarks, suggestionMark("insert", params)],
20
+ });
21
+ /**
22
+ * Marks a selection's content with a "delete"-kind suggestion instead of
23
+ * actually deleting it - the content stays in the document (rendered
24
+ * struck-through) until the suggestion is accepted. Reuses
25
+ * applyMarkCommand directly: a suggestion mark is, mechanically, just
26
+ * another mark, so the existing per-owner range splitting/exclusion
27
+ * machinery already does exactly what's needed for a selection spanning
28
+ * multiple paragraphs.
29
+ */
30
+ export const suggestDeleteCommand = (document, scope, params, ctx) => applyMarkCommand(document, scope, { markType: "suggestion", attrs: suggestionMark("delete", params).attrs }, ctx);
31
+ /**
32
+ * The single-range counterpart to suggestDeleteCommand, for a caller that
33
+ * already has a concrete single-owner SmartRange in hand (e.g. the input
34
+ * pipeline's ambient track-changes mode) and has no ResolvedScope to
35
+ * resolve - one addMark operation, no scope/positions machinery involved.
36
+ */
37
+ export const suggestDeleteRangeOperation = (range, params) => ({
38
+ type: "addMark", range, mark: suggestionMark("delete", params),
39
+ });
40
+ /**
41
+ * Finds every contiguous run of text carrying a suggestion mark with the
42
+ * given id, anywhere in the document - not scoped to a caret position
43
+ * (unlike resolveMarkRun), since accept/reject is driven by a UI marker
44
+ * click, not necessarily a caret inside the run. A suggestion can in
45
+ * principle be split across owners by an intervening structural edit
46
+ * (rare), so this returns one entry per owner it's found in, not
47
+ * assuming a single contiguous span.
48
+ */
49
+ export const findSuggestionRuns = (document, id) => {
50
+ const runs = [];
51
+ const visit = (node, path) => {
52
+ if (isTextNode(node))
53
+ return;
54
+ const children = node.children || [];
55
+ if (children.some((child) => isTextNode(child))) {
56
+ let offset = 0;
57
+ let current = null;
58
+ const flush = (to) => {
59
+ if (current)
60
+ runs.push({ ownerNodeId: node.id, range: { from: { path: [...path], offset: current.from }, to: { path: [...path], offset: to } }, mark: current.mark, text: current.text });
61
+ current = null;
62
+ };
63
+ children.forEach((child) => {
64
+ const width = isTextNode(child) ? child.text.length : 1;
65
+ const mark = isTextNode(child) ? child.marks?.find((candidate) => candidate.type === "suggestion" && candidate.attrs?.id === id) : undefined;
66
+ if (mark && isTextNode(child))
67
+ current = current ? { ...current, text: current.text + child.text } : { from: offset, mark, text: child.text };
68
+ else
69
+ flush(offset);
70
+ offset += width;
71
+ });
72
+ flush(offset);
73
+ }
74
+ children.forEach((child, index) => visit(child, [...path, index]));
75
+ };
76
+ visit(document, []);
77
+ return runs;
78
+ };
79
+ // deleteText ops targeting the same owner must run back-to-front, or an
80
+ // earlier deletion shifts the offsets a later one still expects.
81
+ const byDescendingPosition = (a, b) => -comparePos(a.range.from, b.range.from);
82
+ /** Accept: "insert" runs keep their text (mark stripped); "delete" runs are actually removed. */
83
+ export const acceptSuggestionCommand = (document, id) => [...findSuggestionRuns(document, id)].sort(byDescendingPosition).map((run) => run.mark.attrs?.kind === "delete"
84
+ ? { type: "deleteText", pos: run.range.from, text: run.text }
85
+ : { type: "removeMark", range: run.range, mark: run.mark });
86
+ /** Reject: "insert" runs are removed (the proposed addition never happened); "delete" runs keep their text (mark stripped). */
87
+ export const rejectSuggestionCommand = (document, id) => [...findSuggestionRuns(document, id)].sort(byDescendingPosition).map((run) => run.mark.attrs?.kind === "insert"
88
+ ? { type: "deleteText", pos: run.range.from, text: run.text }
89
+ : { type: "removeMark", range: run.range, mark: run.mark });
90
+ /** One entry per distinct suggestion id found anywhere in the document - the panel-listing counterpart to findSuggestionRuns' by-id lookup. */
91
+ export const listInlineSuggestions = (document) => {
92
+ const summaries = new Map();
93
+ const visit = (node) => {
94
+ if (isTextNode(node))
95
+ return;
96
+ (node.children || []).forEach((child) => {
97
+ if (isTextNode(child)) {
98
+ const mark = child.marks?.find((candidate) => candidate.type === "suggestion");
99
+ if (mark?.attrs) {
100
+ const id = String(mark.attrs.id);
101
+ const existing = summaries.get(id);
102
+ summaries.set(id, {
103
+ id,
104
+ authorId: String(mark.attrs.authorId),
105
+ kind: mark.attrs.kind,
106
+ createdAt: Number(mark.attrs.createdAt),
107
+ text: (existing?.text || "") + child.text,
108
+ });
109
+ }
110
+ }
111
+ visit(child);
112
+ });
113
+ };
114
+ visit(document);
115
+ return [...summaries.values()];
116
+ };
@@ -0,0 +1,40 @@
1
+ import type { AnnotationRange } from "../annotations/types.js";
2
+ import type { PositionLookup } from "../scope/types.js";
3
+ import type { SmartDocument, SmartOperation, SmartSchema, SmartTransaction } from "../types.js";
4
+ /**
5
+ * A proposed removal of a whole structural node (a paragraph, list item,
6
+ * table row, ...) - the AnnotationRange half of Phase 12a §2.3's hybrid
7
+ * architecture (2026-08-24 decision). Unlike an inline suggestion (a mark
8
+ * on real text, see inline.ts), the node stays exactly as it is in the
9
+ * live document until accepted - there is no equivalent "tag the node and
10
+ * insert it for real" mechanism for a *new* structural node yet (see the
11
+ * module-level limitation note below); only proposed removal is supported.
12
+ *
13
+ * `range` always anchors both endpoints to the same node id (a whole-node
14
+ * span, offsets undefined) - the exact same shape CommentMarkers/
15
+ * resolveAnnotationRange already render and rebase correctly for whole-
16
+ * node anchors.
17
+ */
18
+ export interface StructuralSuggestion {
19
+ readonly id: string;
20
+ readonly authorId: string;
21
+ readonly kind: "removeNode";
22
+ readonly range: AnnotationRange;
23
+ readonly createdAt: number;
24
+ }
25
+ export declare const structuralSuggestionFromNode: (nodeId: string, authorId: string, opts?: {
26
+ readonly id?: string;
27
+ readonly createdAt?: number;
28
+ }) => StructuralSuggestion;
29
+ /** Accept: actually removes the node. No-op (empty operations) if it no longer resolves - already gone, nothing to do. */
30
+ export declare const acceptStructuralSuggestionCommand: (document: SmartDocument, suggestion: StructuralSuggestion, positions: PositionLookup) => SmartOperation[];
31
+ /**
32
+ * Replays a transaction's operations to keep every suggestion's range
33
+ * anchored, exactly mirroring comments/rebase.ts's
34
+ * rebaseCommentThreadsThroughTransaction (same reasoning: a fresh
35
+ * PositionLookup per operation, reference-identity preserved for untouched
36
+ * suggestions). Kept as a separate, structurally-parallel implementation
37
+ * rather than sharing code with comments/rebase.ts, to avoid touching that
38
+ * already-shipped, already-tested module for this.
39
+ */
40
+ export declare const rebaseStructuralSuggestionsThroughTransaction: (suggestions: readonly StructuralSuggestion[], beforeDocument: SmartDocument, transaction: SmartTransaction, schema: SmartSchema) => readonly StructuralSuggestion[];
@@ -0,0 +1,47 @@
1
+ import { createNodeId } from "../identity.js";
2
+ import { applyOperation } from "../operations.js";
3
+ import { createScopeIndex } from "../scope/resolveScope.js";
4
+ import { defaultMergeOrphanPolicy, rebaseAnnotationRange, resolveAnnotationRange } from "../annotations/range.js";
5
+ export const structuralSuggestionFromNode = (nodeId, authorId, opts = {}) => ({
6
+ id: opts.id || createNodeId(),
7
+ authorId,
8
+ kind: "removeNode",
9
+ range: { startId: nodeId, endId: nodeId },
10
+ createdAt: opts.createdAt ?? Date.now(),
11
+ });
12
+ /** Accept: actually removes the node. No-op (empty operations) if it no longer resolves - already gone, nothing to do. */
13
+ export const acceptStructuralSuggestionCommand = (document, suggestion, positions) => {
14
+ const resolvedRange = resolveAnnotationRange(suggestion.range, positions);
15
+ if (!resolvedRange)
16
+ return [];
17
+ const resolved = positions.positionOf(suggestion.range.startId);
18
+ const node = resolved?.parent.children?.[resolved.pos.offset];
19
+ if (!resolved || !node)
20
+ return [];
21
+ return [{ type: "removeNode", pos: resolved.pos, node }];
22
+ };
23
+ // Reject never touches the document - a proposed removal never removed
24
+ // anything, so discarding the StructuralSuggestion record itself (a
25
+ // caller/storage concern, not a core one) is the entire "reject" action.
26
+ /**
27
+ * Replays a transaction's operations to keep every suggestion's range
28
+ * anchored, exactly mirroring comments/rebase.ts's
29
+ * rebaseCommentThreadsThroughTransaction (same reasoning: a fresh
30
+ * PositionLookup per operation, reference-identity preserved for untouched
31
+ * suggestions). Kept as a separate, structurally-parallel implementation
32
+ * rather than sharing code with comments/rebase.ts, to avoid touching that
33
+ * already-shipped, already-tested module for this.
34
+ */
35
+ export const rebaseStructuralSuggestionsThroughTransaction = (suggestions, beforeDocument, transaction, schema) => {
36
+ if (!suggestions.length || !transaction.operations.length)
37
+ return suggestions;
38
+ let runningDocument = beforeDocument;
39
+ let ranges = suggestions.map((suggestion) => suggestion.range);
40
+ for (const operation of transaction.operations) {
41
+ const positions = createScopeIndex().positions(runningDocument, schema);
42
+ const onOrphan = (range, mergeOp, lookup) => defaultMergeOrphanPolicy(runningDocument)(range, mergeOp, lookup) ?? range;
43
+ ranges = ranges.map((range) => rebaseAnnotationRange(range, operation, positions, onOrphan) ?? range);
44
+ runningDocument = applyOperation(runningDocument, operation);
45
+ }
46
+ return suggestions.map((suggestion, index) => ranges[index] === suggestion.range ? suggestion : { ...suggestion, range: ranges[index] });
47
+ };
@@ -0,0 +1,19 @@
1
+ export type SuggestionKind = "insert" | "delete";
2
+ /**
3
+ * Attrs carried on the "suggestion" text mark (Phase 12a §2.3, hybrid
4
+ * architecture per user decision 2026-08-24): an inline suggestion is real
5
+ * content in the live document, tagged the same way bold/italic/link are,
6
+ * rather than a separate not-yet-applied proposal. "insert" marks text that
7
+ * was actually inserted, pending acceptance; "delete" marks text that is
8
+ * still present but proposed for removal. This is deliberately NOT a
9
+ * separate SuggestionThread-style out-of-band record the way CommentThread
10
+ * is - reusing the marks pipeline means split/merge/undo/rendering all
11
+ * already work for free (see marks/dom.ts, operations.ts's generic mark
12
+ * carry-through).
13
+ */
14
+ export interface SuggestionMarkAttrs {
15
+ readonly id: string;
16
+ readonly authorId: string;
17
+ readonly kind: SuggestionKind;
18
+ readonly createdAt: number;
19
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export * from "./types.js";
2
+ export { createSubtreeRenderer, FoundationSubtreeRenderer } from "./renderer.js";
3
+ export { createInputPipeline, FoundationInputPipeline } from "./input.js";