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,422 @@
1
+ import { cloneNode, isTextNode } from "../identity.js";
2
+ import { moveContiguousSiblings } from "../structural/move.js";
3
+ import { isFoundationSmartListPreset } from "./presets.js";
4
+ const attrsOf = (node) => ({ ...(node.attrs || {}) });
5
+ const withAttrs = (node, attrs) => {
6
+ const clean = Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== undefined));
7
+ const { attrs: _attrs, ...rest } = node;
8
+ return Object.keys(clean).length ? { ...rest, attrs: clean } : rest;
9
+ };
10
+ const withChildren = (node, children) => ({ ...node, children });
11
+ const locate = (document, id, ctx) => {
12
+ const resolved = ctx.positions.positionOf(id);
13
+ if (!resolved)
14
+ throw new Error(`Unknown node ID "${id}".`);
15
+ const node = resolved.parent.children?.[resolved.pos.offset];
16
+ if (!node || isTextNode(node) || node.id !== id)
17
+ throw new Error(`PositionLookup returned a stale boundary for "${id}".`);
18
+ return { node, pos: { path: [...resolved.pos.path], offset: resolved.pos.offset } };
19
+ };
20
+ const listScopes = (scope) => {
21
+ if (scope.kind === "list-selection")
22
+ return [scope];
23
+ if (scope.kind !== "mixed")
24
+ return [];
25
+ const collect = (mixed) => mixed.parts.flatMap((part) => part.kind === "list-selection" ? [part] : part.kind === "mixed" ? collect(part) : []);
26
+ return collect(scope);
27
+ };
28
+ const blockIds = (scope) => {
29
+ if (scope.kind === "block-range")
30
+ return [...scope.blockIds];
31
+ if (scope.kind !== "mixed")
32
+ return [];
33
+ return scope.parts.flatMap((part) => blockIds(part));
34
+ };
35
+ const directItemIndexes = (list, scope) => {
36
+ const wanted = new Set(scope.items.map((entry) => entry.itemId));
37
+ return (list.children || []).flatMap((node, index) => !isTextNode(node) && wanted.has(node.id) ? [index] : []);
38
+ };
39
+ const contiguousRuns = (values) => {
40
+ const sorted = [...new Set(values)].sort((a, b) => a - b);
41
+ const runs = [];
42
+ sorted.forEach((value) => {
43
+ const last = runs[runs.length - 1];
44
+ if (last && value === last.end + 1)
45
+ last.end = value;
46
+ else
47
+ runs.push({ start: value, end: value });
48
+ });
49
+ return runs;
50
+ };
51
+ const replacementSequence = (before, pos, after) => {
52
+ if (!after.length)
53
+ return [{ type: "removeNode", pos, node: before }];
54
+ const operations = [{ type: "replaceNode", pos, before, after: after[0] }];
55
+ after.slice(1).forEach((node, index) => operations.push({
56
+ type: "insertNode",
57
+ pos: { path: [...pos.path], offset: pos.offset + index + 1 },
58
+ node,
59
+ }));
60
+ return operations;
61
+ };
62
+ const checkIds = (ids, count, label) => {
63
+ if (!ids || ids.length < count)
64
+ throw new Error(`${label} requires ${count} caller-provided deterministic node ID(s).`);
65
+ return ids;
66
+ };
67
+ /** How many list ancestors sit above this list — 0 for a top-level list, 1 for
68
+ * a list nested one level inside a list_item, and so on. Used to stamp the
69
+ * original nesting depth onto content unwrapped out of a list, so a later
70
+ * list.create over that content (and other unwrapped content) can rebuild it. */
71
+ const listAncestorDepth = (list, ctx) => {
72
+ let depth = 0;
73
+ let current = list;
74
+ for (;;) {
75
+ const resolved = ctx.positions.positionOf(current.id);
76
+ if (!resolved || resolved.parent.type !== "list_item")
77
+ return depth;
78
+ const outer = ctx.positions.positionOf(resolved.parent.id)?.parent;
79
+ if (!outer || outer.type !== "list")
80
+ return depth;
81
+ depth += 1;
82
+ current = outer;
83
+ }
84
+ };
85
+ const withDepthStamp = (node, depth) => depth <= 0 || typeof node.attrs?.indentLevel === "number" ? node : withAttrs(node, { ...attrsOf(node), indentLevel: depth });
86
+ /** Explicit style is the current-level override; preset supplies other levels. */
87
+ export const effectiveListStyle = (list) => typeof list.attrs?.style === "string" ? list.attrs.style
88
+ : typeof list.attrs?.preset === "string" ? list.attrs.preset
89
+ : undefined;
90
+ const withoutIndentLevel = (node) => typeof node.attrs?.indentLevel === "number" ? withAttrs(node, { ...attrsOf(node), indentLevel: undefined }) : node;
91
+ /** Clamps each block's indentLevel to at most one deeper than the block
92
+ * immediately before it (mirroring indentList's "only nest under the
93
+ * immediately preceding sibling" rule), so a selection built from a mix of
94
+ * never-nested content and content previously unwrapped out of a list
95
+ * (see withDepthStamp) always yields a structurally valid outline. */
96
+ const clampedDepths = (nodes) => {
97
+ let previous = -1;
98
+ return nodes.map((node) => {
99
+ const raw = Math.max(0, Number(node.attrs?.indentLevel) || 0);
100
+ const depth = Math.min(raw, previous + 1);
101
+ previous = depth;
102
+ return depth;
103
+ });
104
+ };
105
+ /** Rebuilds a run of blocks at a common depth into list_items, recursing into
106
+ * a nested list for any run of consecutively deeper blocks that follows one
107
+ * of them — the reconstruction counterpart to withDepthStamp. */
108
+ const buildListItems = (entries, depths, base, nextItemId, nextListId, listAttrs) => {
109
+ const items = [];
110
+ let index = 0;
111
+ while (index < entries.length) {
112
+ const entry = entries[index];
113
+ // Consume this item's own ID before descending into any deeper run, so
114
+ // IDs are assigned in document order rather than the deepest/last entry
115
+ // grabbing an earlier ID than an entry that appears before it.
116
+ const itemId = nextItemId();
117
+ const content = withoutIndentLevel(cloneNode(entry.node));
118
+ index += 1;
119
+ const deepStart = index;
120
+ while (index < entries.length && depths[index] > base)
121
+ index += 1;
122
+ const children = [content];
123
+ if (index > deepStart)
124
+ children.push({
125
+ type: "list",
126
+ id: nextListId(),
127
+ attrs: listAttrs,
128
+ children: buildListItems(entries.slice(deepStart, index), depths.slice(deepStart, index), base + 1, nextItemId, nextListId, listAttrs),
129
+ });
130
+ items.push({
131
+ type: "list_item",
132
+ id: itemId,
133
+ ...(typeof entry.node.attrs?.htmlStyle === "string" ? { attrs: { htmlStyle: entry.node.attrs.htmlStyle } } : {}),
134
+ children,
135
+ });
136
+ }
137
+ return items;
138
+ };
139
+ export const createList = (document, scope, params, ctx) => {
140
+ if (params.preset !== undefined && !isFoundationSmartListPreset(params.preset))
141
+ return [];
142
+ const ids = blockIds(scope);
143
+ if (!ids.length)
144
+ return [];
145
+ const located = ids.map((id) => ({ id, ...locate(document, id, ctx) }));
146
+ const byParent = new Map();
147
+ located.forEach((entry) => {
148
+ const key = JSON.stringify(entry.pos.path);
149
+ byParent.set(key, [...(byParent.get(key) || []), entry]);
150
+ });
151
+ const groups = [...byParent.values()].flatMap((entries) => {
152
+ const byOffset = new Map(entries.map((entry) => [entry.pos.offset, entry]));
153
+ return contiguousRuns(entries.map((entry) => entry.pos.offset)).map((run) => ({
154
+ path: entries[0].pos.path,
155
+ entries: Array.from({ length: run.end - run.start + 1 }, (_, index) => byOffset.get(run.start + index)),
156
+ }));
157
+ }).sort((left, right) => right.entries[0].pos.offset - left.entries[0].pos.offset);
158
+ const itemIds = checkIds(params.itemIds, located.length, "list.create");
159
+ let itemCursor = 0;
160
+ let listCursor = 0;
161
+ // A reconstructed group may need more than one list node (one per nesting
162
+ // transition), which isn't known until the content's depths are read. The
163
+ // caller over-provisions listIds; this consumes only what's needed and
164
+ // fails clearly, matching indentList's per-step checkIds pattern, if it
165
+ // genuinely runs out.
166
+ const nextListId = () => checkIds(params.listIds, listCursor + 1, "list.create")[listCursor++];
167
+ const listAttrs = {
168
+ ...(params.preset !== undefined ? { preset: params.preset } : {}),
169
+ ...(params.style !== undefined ? { style: params.style } : {}),
170
+ ...(params.start !== undefined ? { start: params.start } : {}),
171
+ ...(params.checkable !== undefined ? { checkable: params.checkable } : {}),
172
+ };
173
+ return groups.flatMap((group) => {
174
+ const depths = clampedDepths(group.entries.map((entry) => entry.node));
175
+ const list = {
176
+ type: "list",
177
+ id: nextListId(),
178
+ attrs: listAttrs,
179
+ children: buildListItems(group.entries, depths, 0, () => itemIds[itemCursor++], nextListId, listAttrs),
180
+ };
181
+ const first = group.entries[0];
182
+ const operations = [{ type: "replaceNode", pos: first.pos, before: first.node, after: list }];
183
+ group.entries.slice(1).forEach((entry) => operations.push({
184
+ type: "removeNode",
185
+ pos: { path: [...first.pos.path], offset: first.pos.offset + 1 },
186
+ node: entry.node,
187
+ }));
188
+ return operations;
189
+ });
190
+ };
191
+ const unwrapOne = (list, selectedIndexes, splitId, depth) => {
192
+ if (!selectedIndexes.length)
193
+ return [];
194
+ const selected = new Set(selectedIndexes);
195
+ const runs = contiguousRuns(selectedIndexes);
196
+ if (runs.length !== 1)
197
+ throw new Error("list.unwrap requires one contiguous item run per list.");
198
+ const { start, end } = runs[0];
199
+ const children = list.node.children || [];
200
+ const beforeItems = children.slice(0, start);
201
+ const afterItems = children.slice(end + 1);
202
+ const unwrapped = children.slice(start, end + 1).flatMap((node) => {
203
+ if (isTextNode(node))
204
+ throw new Error("List children must be list items.");
205
+ // Unwrapping removes the list-item wrapper, not its descendants. Nested
206
+ // lists become sibling block content at the new level; dropping them here
207
+ // loses user content and leaves a later mixed-scope selection with no list
208
+ // part to act on. The top-level unwrapped children are stamped with the
209
+ // list's nesting depth so a later list.create can rebuild it; a nested
210
+ // list carried along unchanged still encodes its own relative depth via
211
+ // its own item structure, so it is left alone.
212
+ return (node.children || []).map((child) => {
213
+ const cloned = cloneNode(child);
214
+ return isTextNode(cloned) || cloned.type === "list" ? cloned : withDepthStamp(cloned, depth);
215
+ });
216
+ });
217
+ const sequence = [];
218
+ if (beforeItems.length)
219
+ sequence.push(withChildren(list.node, beforeItems));
220
+ sequence.push(...unwrapped);
221
+ if (afterItems.length) {
222
+ const afterId = beforeItems.length ? splitId : list.node.id;
223
+ if (!afterId)
224
+ throw new Error("list.unwrap middle split requires a caller-provided splitListId.");
225
+ // A middle unwrap splits an ordered list into two list nodes — without
226
+ // this, the trailing portion silently restarts its own numbering at 1
227
+ // instead of continuing from where the original list left off.
228
+ // Harmless to set on an unordered list; `start` is simply unused there.
229
+ const priorStart = typeof list.node.attrs?.start === "number" ? list.node.attrs.start : 1;
230
+ const continuedStart = priorStart + end + 1;
231
+ sequence.push(withAttrs({ ...list.node, id: afterId, children: afterItems }, { ...attrsOf(list.node), start: continuedStart }));
232
+ }
233
+ if (selected.size !== end - start + 1)
234
+ throw new Error("Invalid selected list item indexes.");
235
+ return replacementSequence(list.node, list.pos, sequence);
236
+ };
237
+ export const unwrapList = (document, scope, params, ctx) => {
238
+ const scopes = listScopes(scope);
239
+ let splitCursor = 0;
240
+ return scopes.map((entry) => ({ entry, list: locate(document, entry.listId, ctx) }))
241
+ .sort((a, b) => b.list.pos.offset - a.list.pos.offset)
242
+ .flatMap(({ entry, list }) => {
243
+ const indexes = directItemIndexes(list.node, entry);
244
+ const middle = indexes.length && indexes[0] > 0 && indexes[indexes.length - 1] < (list.node.children?.length || 0) - 1;
245
+ const splitId = middle ? params.splitListIds?.[splitCursor++] : undefined;
246
+ return unwrapOne(list, indexes, splitId, listAncestorDepth(list.node, ctx));
247
+ });
248
+ };
249
+ const nestedListChild = (item) => {
250
+ const children = item.children || [];
251
+ for (let index = children.length - 1; index >= 0; index -= 1) {
252
+ const child = children[index];
253
+ if (!isTextNode(child) && child.type === "list")
254
+ return { node: child, index };
255
+ }
256
+ return null;
257
+ };
258
+ const compatibleNestedList = (parent, candidate) => {
259
+ const parentOrdered = /^(?:decimal|lower-|upper-|ordered)/.test(String(parent.attrs?.style || parent.attrs?.preset || ""));
260
+ const candidateOrdered = /^(?:decimal|lower-|upper-|ordered)/.test(String(candidate.attrs?.style || candidate.attrs?.preset || ""));
261
+ return parentOrdered === candidateOrdered && parent.attrs?.checkable === candidate.attrs?.checkable;
262
+ };
263
+ /** Indent is a decision about the selected line(s), not their whole subtree.
264
+ * A moved item's own nested list is unwrapped one level rather than carried
265
+ * along — its direct children become siblings of the moved item at its new
266
+ * position, in the same order, instead of silently ending up one level
267
+ * deeper than before. Only the moved item's own nested list unwraps; each
268
+ * hoisted child keeps whatever nested structure it independently has. */
269
+ const flattenMovedItem = (node) => {
270
+ if (isTextNode(node))
271
+ throw new Error("List children must be list items.");
272
+ const item = node;
273
+ const nested = nestedListChild(item);
274
+ if (!nested)
275
+ return [item];
276
+ const withoutNested = withChildren(item, (item.children || []).filter((_, index) => index !== nested.index));
277
+ const hoisted = (nested.node.children || []).filter((child) => !isTextNode(child));
278
+ return [withoutNested, ...hoisted];
279
+ };
280
+ export const indentList = (document, scope, params, ctx) => {
281
+ let idCursor = 0;
282
+ return listScopes(scope).flatMap((entry) => {
283
+ const located = locate(document, entry.listId, ctx);
284
+ const indexes = directItemIndexes(located.node, entry);
285
+ const runs = contiguousRuns(indexes);
286
+ if (runs.length !== 1 || runs[0].start === 0)
287
+ return [];
288
+ const { start, end } = runs[0];
289
+ const children = [...(located.node.children || [])];
290
+ const previous = children[start - 1];
291
+ if (!previous || isTextNode(previous))
292
+ throw new Error("List item predecessor is invalid.");
293
+ const moved = children.slice(start, end + 1).flatMap(flattenMovedItem);
294
+ const candidate = nestedListChild(previous);
295
+ const nested = candidate && compatibleNestedList(located.node, candidate.node) ? candidate : null;
296
+ let nextPrevious;
297
+ if (nested) {
298
+ const nextNested = withChildren(nested.node, [...(nested.node.children || []), ...moved]);
299
+ nextPrevious = withChildren(previous, (previous.children || []).map((child, index) => index === nested.index ? nextNested : child));
300
+ }
301
+ else {
302
+ const nestedId = checkIds(params.nestedListIds, idCursor + 1, "list.indent")[idCursor++];
303
+ const nestedNode = { ...located.node, id: nestedId, children: moved };
304
+ nextPrevious = withChildren(previous, [...(previous.children || []), nestedNode]);
305
+ }
306
+ const nextChildren = [...children.slice(0, start - 1), nextPrevious, ...children.slice(end + 1)];
307
+ return [{ type: "replaceNode", pos: located.pos, before: located.node, after: withChildren(located.node, nextChildren) }];
308
+ });
309
+ };
310
+ export const outdentList = (document, scope, params, ctx) => {
311
+ let splitCursor = 0;
312
+ return listScopes(scope).flatMap((entry) => {
313
+ const nested = locate(document, entry.listId, ctx);
314
+ const indexes = directItemIndexes(nested.node, entry);
315
+ if (!indexes.length)
316
+ return [];
317
+ const parentItemResolved = ctx.positions.positionOf(nested.node.id)?.parent;
318
+ if (!parentItemResolved || parentItemResolved.type !== "list_item") {
319
+ const middle = indexes[0] > 0 && indexes[indexes.length - 1] < (nested.node.children?.length || 0) - 1;
320
+ return unwrapOne(nested, indexes, middle ? params.splitListIds?.[splitCursor++] : undefined, listAncestorDepth(nested.node, ctx));
321
+ }
322
+ const parentItem = parentItemResolved;
323
+ const parentListResolved = ctx.positions.positionOf(parentItem.id)?.parent;
324
+ if (!parentListResolved || parentListResolved.type !== "list")
325
+ throw new Error("Nested list does not belong to a parent list item.");
326
+ const parentList = locate(document, parentListResolved.id, ctx);
327
+ const parentItemIndex = (parentList.node.children || []).findIndex((child) => !isTextNode(child) && child.id === parentItem.id);
328
+ const selected = new Set(indexes);
329
+ const moved = (nested.node.children || []).filter((_, index) => selected.has(index));
330
+ const remaining = (nested.node.children || []).filter((_, index) => !selected.has(index));
331
+ const updatedParentItem = withChildren(parentItem, (parentItem.children || []).flatMap((child) => {
332
+ if (isTextNode(child) || child.id !== nested.node.id)
333
+ return [child];
334
+ return remaining.length ? [withChildren(nested.node, remaining)] : [];
335
+ }));
336
+ const nextParentChildren = [...(parentList.node.children || [])];
337
+ nextParentChildren[parentItemIndex] = updatedParentItem;
338
+ nextParentChildren.splice(parentItemIndex + 1, 0, ...moved);
339
+ return [{ type: "replaceNode", pos: parentList.pos, before: parentList.node, after: withChildren(parentList.node, nextParentChildren) }];
340
+ });
341
+ };
342
+ const replaceLists = (document, scope, ctx, change) => listScopes(scope).map((entry) => ({ entry, list: locate(document, entry.listId, ctx) }))
343
+ .map(({ entry, list }) => ({ type: "replaceNode", pos: list.pos, before: list.node, after: change(list.node, entry) }));
344
+ // Marker CSS is keyed on `[data-srte-list-preset][data-srte-list-depth]` on
345
+ // each list element individually — depth alone does not select a marker
346
+ // without a matching preset on that same nested list. A style/preset change
347
+ // must therefore cascade into every nested list, not just the selected one,
348
+ // or descendant items keep rendering their old marker family.
349
+ const withNestedListsRestyled = (list, restyle) => withChildren(restyle(list), (list.children || []).map((item) => {
350
+ if (isTextNode(item) || item.type !== "list_item")
351
+ return item;
352
+ return withChildren(item, (item.children || []).map((child) => !isTextNode(child) && child.type === "list" ? withNestedListsRestyled(child, restyle) : child));
353
+ }));
354
+ export const setListPreset = (document, scope, params, ctx) => (params.preset !== undefined && !isFoundationSmartListPreset(params.preset)
355
+ ? []
356
+ : replaceLists(document, scope, ctx, (list) => withNestedListsRestyled(list, (nested) => withAttrs(nested, { ...attrsOf(nested), preset: params.preset, style: undefined }))));
357
+ export const setListStyle = (document, scope, params, ctx) => replaceLists(document, scope, ctx, (list) => {
358
+ // A concrete style is a state transition, not an additional marker. A
359
+ // stale preset makes serializers and CSS consumers see two competing list
360
+ // definitions after numbered → bulleted/checklist changes. Style/preset
361
+ // cascade to nested lists (see withNestedListsRestyled); `checkable` does
362
+ // not — converting an outer list to a checklist should not silently make
363
+ // every nested sublist checkable too.
364
+ const restyled = withNestedListsRestyled(list, (nested) => withAttrs(nested, { ...attrsOf(nested), preset: undefined, style: params.style }));
365
+ return params.checkable !== undefined ? withAttrs(restyled, { ...attrsOf(restyled), checkable: params.checkable }) : restyled;
366
+ });
367
+ export const setListChecked = (document, scope, params, ctx) => replaceLists(document, scope, ctx, (list, entry) => {
368
+ if (list.attrs?.checkable !== true)
369
+ return list;
370
+ const selected = new Set(entry.items.map((item) => item.itemId));
371
+ return withChildren(list, (list.children || []).map((item) => {
372
+ if (isTextNode(item) || !selected.has(item.id))
373
+ return item;
374
+ return withAttrs(item, { ...attrsOf(item), checked: params.checked });
375
+ }));
376
+ });
377
+ /** Moves one contiguous item run by one sibling while preserving every item subtree and ID. */
378
+ export const moveListItems = (_document, scope, params, ctx) => listScopes(scope).flatMap((entry) => moveContiguousSiblings(entry.items.map((item) => item.itemId), params.direction, ctx));
379
+ export const restartListNumbering = (document, scope, params, ctx) => {
380
+ if (!Number.isInteger(params.start) || params.start < 1)
381
+ throw new Error("list.restartNumbering requires a positive integer.");
382
+ return replaceLists(document, scope, ctx, (list) => withAttrs(list, { ...attrsOf(list), start: params.start }));
383
+ };
384
+ export const continueListNumbering = (document, scope, _params, ctx) => replaceLists(document, scope, ctx, (list) => withAttrs(list, { ...attrsOf(list), start: undefined }));
385
+ export const insertListFragment = (document, scope, params, ctx) => {
386
+ const targetScope = listScopes(scope)[0];
387
+ if (!targetScope)
388
+ return [];
389
+ const located = locate(document, targetScope.listId, ctx);
390
+ const direct = directItemIndexes(located.node, targetScope);
391
+ if (!direct.length && params.position !== "start" && params.position !== "end")
392
+ return [];
393
+ const fragmentNodes = params.fragment.children.flatMap((node) => {
394
+ if (!isTextNode(node) && node.type === "list")
395
+ return (node.children || []).map(cloneNode);
396
+ return [cloneNode(node)];
397
+ });
398
+ const plainCount = fragmentNodes.filter((node) => isTextNode(node) || node.type !== "list_item").length;
399
+ const itemIds = plainCount ? checkIds(params.itemIds, plainCount, "list fragment insertion") : [];
400
+ let itemCursor = 0;
401
+ const items = fragmentNodes.map((node) => !isTextNode(node) && node.type === "list_item" ? node : ({
402
+ type: "list_item", id: itemIds[itemCursor++], children: [node],
403
+ }));
404
+ const insertionIndex = params.position === "start" ? 0
405
+ : params.position === "end" ? (located.node.children?.length || 0)
406
+ : params.position === "before" ? direct[0] : direct[direct.length - 1] + 1;
407
+ const children = [...(located.node.children || [])];
408
+ children.splice(insertionIndex, 0, ...items);
409
+ return [{ type: "replaceNode", pos: located.pos, before: located.node, after: withChildren(located.node, children) }];
410
+ };
411
+ export const listCommands = {
412
+ "list.create": createList,
413
+ "list.unwrap": unwrapList,
414
+ "list.indent": indentList,
415
+ "list.outdent": outdentList,
416
+ "list.setPreset": setListPreset,
417
+ "list.setStyle": setListStyle,
418
+ "list.setChecked": setListChecked,
419
+ "list.move": moveListItems,
420
+ "list.restartNumbering": restartListNumbering,
421
+ "list.continueNumbering": continueListNumbering,
422
+ };
@@ -0,0 +1,20 @@
1
+ import type { SmartDocument } from "../types.js";
2
+ export declare const serializeCanonicalListHtml: (document: SmartDocument, options?: {
3
+ clean?: boolean;
4
+ fragment?: boolean;
5
+ }) => string;
6
+ export declare const parseCanonicalListHtml: (html: string) => SmartDocument;
7
+ /** Alignment and indent are unsupported in Markdown; block content survives semantically. */
8
+ export declare const serializeCanonicalListMarkdown: (document: SmartDocument) => string;
9
+ export declare const parseCanonicalListMarkdown: (markdown: string) => SmartDocument;
10
+ export interface CanonicalDocxNumberingEntry {
11
+ readonly itemId: string;
12
+ readonly numId: number;
13
+ readonly ilvl: number;
14
+ readonly marker: "bullet" | "decimal";
15
+ readonly text: string;
16
+ readonly checked?: boolean;
17
+ }
18
+ /** DOCX has numId/ilvl but no Smart preset identity; presets fall back semantically. */
19
+ export declare const canonicalListToDocxNumbering: (document: SmartDocument) => CanonicalDocxNumberingEntry[];
20
+ export declare const canonicalListPdfText: (document: SmartDocument) => string;