@stll/folio-core 0.54.1 → 0.55.0

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 (251) hide show
  1. package/dist/ai-edits/apply.d.ts +1 -1
  2. package/dist/ai-edits/apply.js +40 -32
  3. package/dist/ai-edits/headless.d.ts +1 -2
  4. package/dist/ai-edits/headless.js +13 -27
  5. package/dist/ai-edits/read.d.ts +1 -0
  6. package/dist/ai-edits/read.js +7 -2
  7. package/dist/ai-edits/table-mutation-plan.d.ts +2 -1
  8. package/dist/ai-edits/table-mutation-plan.js +39 -1
  9. package/dist/ai-suggestions/apply.d.ts +1 -1
  10. package/dist/ai-suggestions/apply.js +3 -2
  11. package/dist/compare/compare.js +2 -1
  12. package/dist/compare/content-types.d.ts +3 -15
  13. package/dist/controller/canonicalComposition.d.ts +36 -0
  14. package/dist/controller/canonicalComposition.js +168 -0
  15. package/dist/controller/canonicalInput.d.ts +45 -0
  16. package/dist/controller/canonicalInput.js +338 -0
  17. package/dist/controller/canonicalOperations.d.ts +22 -0
  18. package/dist/controller/canonicalOperations.js +85 -0
  19. package/dist/controller/canonicalReview.d.ts +12 -0
  20. package/dist/controller/canonicalReview.js +19 -0
  21. package/dist/controller/canonicalSession.d.ts +149 -0
  22. package/dist/controller/canonicalSession.js +1038 -0
  23. package/dist/controller/canonicalStoryEditor.d.ts +34 -0
  24. package/dist/controller/canonicalStoryEditor.js +82 -0
  25. package/dist/controller/canonicalStructure.d.ts +16 -0
  26. package/dist/controller/canonicalStructure.js +269 -0
  27. package/dist/controller/folioEditor.d.ts +1 -1
  28. package/dist/controller/folioEditor.js +9 -0
  29. package/dist/controller/headerFooterEditorManager.d.ts +4 -0
  30. package/dist/controller/headerFooterEditorManager.js +71 -3
  31. package/dist/controller/hiddenEditorApi.d.ts +35 -0
  32. package/dist/controller/hiddenEditorApi.js +25 -8
  33. package/dist/controller/hiddenEditorManager.d.ts +11 -2
  34. package/dist/controller/hiddenEditorManager.js +456 -30
  35. package/dist/controller/layoutPipeline.d.ts +1 -1
  36. package/dist/controller/layoutPipeline.js +2 -0
  37. package/dist/controller/noteEditorManager.d.ts +4 -0
  38. package/dist/controller/noteEditorManager.js +113 -15
  39. package/dist/docx/attributeRemainder.js +1 -0
  40. package/dist/docx/bookmarkIds.d.ts +11 -0
  41. package/dist/docx/bookmarkIds.js +33 -0
  42. package/dist/docx/canonicalSessionInput.d.ts +11 -0
  43. package/dist/docx/canonicalSessionInput.js +27 -0
  44. package/dist/docx/canonicalStoryRepack.d.ts +10 -0
  45. package/dist/docx/canonicalStoryRepack.js +41 -0
  46. package/dist/docx/contentTypeRegistry.d.ts +8 -0
  47. package/dist/docx/contentTypeRegistry.js +64 -0
  48. package/dist/docx/documentParser.d.ts +14 -1
  49. package/dist/docx/documentParser.js +17 -4
  50. package/dist/docx/drawingIdNormalization.js +23 -8
  51. package/dist/docx/ensureParaIds.d.ts +12 -1
  52. package/dist/docx/ensureParaIds.js +173 -213
  53. package/dist/docx/foldedListNumberFields.d.ts +85 -0
  54. package/dist/docx/foldedListNumberFields.js +267 -0
  55. package/dist/docx/headerFooterVerbatim.d.ts +14 -1
  56. package/dist/docx/headerFooterVerbatim.js +95 -2
  57. package/dist/docx/listNumberingInstances.js +11 -9
  58. package/dist/docx/noteIds.d.ts +9 -0
  59. package/dist/docx/noteIds.js +17 -0
  60. package/dist/docx/noteReferenceMark.d.ts +3 -1
  61. package/dist/docx/noteReferenceMark.js +15 -14
  62. package/dist/docx/numberingIds.d.ts +14 -0
  63. package/dist/docx/numberingIds.js +16 -0
  64. package/dist/docx/numberingParser.d.ts +3 -1
  65. package/dist/docx/numberingParser.js +17 -3
  66. package/dist/docx/numericIdAllocator.d.ts +12 -0
  67. package/dist/docx/numericIdAllocator.js +33 -0
  68. package/dist/docx/numericIdNormalization.d.ts +16 -0
  69. package/dist/docx/numericIdNormalization.js +174 -0
  70. package/dist/docx/packageParts.d.ts +3 -1
  71. package/dist/docx/packageParts.js +65 -16
  72. package/dist/docx/paragraphMarkPropertyPresence.d.ts +9 -0
  73. package/dist/docx/paragraphMarkPropertyPresence.js +21 -0
  74. package/dist/docx/paragraphParser.js +101 -53
  75. package/dist/docx/paragraphPropertySource.d.ts +15 -1
  76. package/dist/docx/paragraphPropertySource.js +76 -22
  77. package/dist/docx/paragraphTextBoxEnrichment.js +2 -1
  78. package/dist/docx/parser.js +19 -6
  79. package/dist/docx/rasterMime.d.ts +64 -0
  80. package/dist/docx/rasterMime.js +116 -0
  81. package/dist/docx/replyToComment.js +13 -6
  82. package/dist/docx/reviewHistoryNamespace.d.ts +4 -0
  83. package/dist/docx/reviewHistoryNamespace.js +4 -0
  84. package/dist/docx/reviewResolutionProvenance.d.ts +25 -0
  85. package/dist/docx/reviewResolutionProvenance.js +153 -0
  86. package/dist/docx/rezip.d.ts +7 -3
  87. package/dist/docx/rezip.js +121 -52
  88. package/dist/docx/runConsolidator.d.ts +2 -58
  89. package/dist/docx/runConsolidator.js +3 -175
  90. package/dist/docx/runParser.d.ts +2 -1
  91. package/dist/docx/runParser.js +156 -116
  92. package/dist/docx/saveDiagnostics.d.ts +11 -0
  93. package/dist/docx/saveDiagnostics.js +0 -0
  94. package/dist/docx/selectiveSave.d.ts +2 -1
  95. package/dist/docx/selectiveSave.js +14 -1
  96. package/dist/docx/selectiveXmlPatch.d.ts +16 -8
  97. package/dist/docx/selectiveXmlPatch.js +74 -2
  98. package/dist/docx/serializer/headerFooterSerializer.d.ts +5 -1
  99. package/dist/docx/serializer/headerFooterSerializer.js +19 -11
  100. package/dist/docx/serializer/noteSerializer.d.ts +2 -2
  101. package/dist/docx/serializer/noteSerializer.js +24 -8
  102. package/dist/docx/serializer/paragraphSerializer.js +41 -4
  103. package/dist/docx/serializer/partNamespaces.js +6 -3
  104. package/dist/docx/serializer/runSerializer.js +8 -5
  105. package/dist/docx/serializer/trackedChangeAttributes.js +7 -1
  106. package/dist/docx/server/build.js +14 -3
  107. package/dist/docx/server/createBilingualDocument.js +10 -9
  108. package/dist/docx/settingsHeaderFooterUpdate.d.ts +5 -0
  109. package/dist/docx/settingsHeaderFooterUpdate.js +90 -0
  110. package/dist/docx/settingsParser.d.ts +1 -2
  111. package/dist/docx/settingsParser.js +5 -6
  112. package/dist/docx/storyBlockReplay.d.ts +11 -0
  113. package/dist/docx/storyBlockReplay.js +367 -0
  114. package/dist/docx/streamingXmlParser.d.ts +31 -1
  115. package/dist/docx/streamingXmlParser.js +136 -21
  116. package/dist/docx/structuralXmlPatch.js +8 -1
  117. package/dist/docx/unzip.d.ts +3 -1
  118. package/dist/docx/unzip.js +36 -16
  119. package/dist/docx/verbatimCapture.d.ts +3 -1
  120. package/dist/docx/verbatimCapture.js +49 -6
  121. package/dist/i18n/messages/ar.gen.d.ts +5 -0
  122. package/dist/i18n/messages/ar.gen.js +656 -0
  123. package/dist/i18n/messages/cs.gen.d.ts +5 -0
  124. package/dist/i18n/messages/cs.gen.js +656 -0
  125. package/dist/i18n/messages/de.gen.d.ts +5 -0
  126. package/dist/i18n/messages/de.gen.js +656 -0
  127. package/dist/i18n/messages/en.gen.d.ts +5 -0
  128. package/dist/i18n/messages/en.gen.js +656 -0
  129. package/dist/i18n/messages/es.gen.d.ts +5 -0
  130. package/dist/i18n/messages/es.gen.js +656 -0
  131. package/dist/i18n/messages/et.gen.d.ts +5 -0
  132. package/dist/i18n/messages/et.gen.js +656 -0
  133. package/dist/i18n/messages/fr.gen.d.ts +5 -0
  134. package/dist/i18n/messages/fr.gen.js +656 -0
  135. package/dist/i18n/messages/he.gen.d.ts +5 -0
  136. package/dist/i18n/messages/he.gen.js +656 -0
  137. package/dist/i18n/messages/hi.gen.d.ts +5 -0
  138. package/dist/i18n/messages/hi.gen.js +656 -0
  139. package/dist/i18n/messages/hu.gen.d.ts +5 -0
  140. package/dist/i18n/messages/hu.gen.js +656 -0
  141. package/dist/i18n/messages/locales.d.ts +5 -0
  142. package/dist/i18n/messages/locales.js +22 -0
  143. package/dist/i18n/messages/lt.gen.d.ts +5 -0
  144. package/dist/i18n/messages/lt.gen.js +656 -0
  145. package/dist/i18n/messages/lv.gen.d.ts +5 -0
  146. package/dist/i18n/messages/lv.gen.js +656 -0
  147. package/dist/i18n/messages/pl.gen.d.ts +5 -0
  148. package/dist/i18n/messages/pl.gen.js +656 -0
  149. package/dist/i18n/messages/pt-BR.gen.d.ts +5 -0
  150. package/dist/i18n/messages/pt-BR.gen.js +656 -0
  151. package/dist/i18n/messages/sk.gen.d.ts +5 -0
  152. package/dist/i18n/messages/sk.gen.js +656 -0
  153. package/dist/i18n/messages/tr.gen.d.ts +5 -0
  154. package/dist/i18n/messages/tr.gen.js +656 -0
  155. package/dist/i18n/messages/zh-CN.gen.d.ts +5 -0
  156. package/dist/i18n/messages/zh-CN.gen.js +656 -0
  157. package/dist/i18n/messages.d.ts +2 -3
  158. package/dist/i18n/messages.js +1 -19
  159. package/dist/internal/noteMarkerAttrs.d.ts +8 -0
  160. package/dist/internal/noteMarkerAttrs.js +16 -0
  161. package/dist/internal/pageBreakRunSourceDescendantIndex.js +1 -0
  162. package/dist/internal/prosemirrorAttrBoundary.d.ts +21 -0
  163. package/dist/internal/prosemirrorAttrBoundary.js +50 -0
  164. package/dist/layout-bridge/convert/fixedTableColumnWidths.d.ts +6 -0
  165. package/dist/layout-bridge/convert/fixedTableColumnWidths.js +23 -0
  166. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  167. package/dist/layout-bridge/convert/footnoteLayout.js +3 -3
  168. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  169. package/dist/layout-bridge/convert/headerFooterLayout.js +1 -0
  170. package/dist/layout-bridge/convert/noteReferences.d.ts +1 -3
  171. package/dist/layout-bridge/convert/noteReferences.js +1 -6
  172. package/dist/layout-bridge/convert/paragraphRuns.js +2 -4
  173. package/dist/layout-bridge/convert/tableConversion.js +4 -1
  174. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +2 -2
  175. package/dist/layout-bridge/convert/toFlowBlocks.js +2 -2
  176. package/dist/layout-painter/index.js +2 -5
  177. package/dist/managers/DocumentLoaderManager.d.ts +1 -0
  178. package/dist/managers/DocumentLoaderManager.js +8 -1
  179. package/dist/markdown/renderBlock.js +15 -1
  180. package/dist/markdown/renderParagraph.js +9 -13
  181. package/dist/markdown/renderRuns.js +3 -2
  182. package/dist/markdown/renderTable.js +2 -0
  183. package/dist/markdown/types.d.ts +3 -9
  184. package/dist/paged-layout/editorScrollRoot.d.ts +15 -0
  185. package/dist/paged-layout/editorScrollRoot.js +72 -0
  186. package/dist/paged-layout/scrollToPmPosition.js +8 -22
  187. package/dist/prosemirror/attrs/index.d.ts +2 -12
  188. package/dist/prosemirror/attrs/index.js +22 -48
  189. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +2 -1
  190. package/dist/prosemirror/canonicalCommands.d.ts +97 -0
  191. package/dist/prosemirror/canonicalCommands.js +28 -0
  192. package/dist/prosemirror/commands/clearParagraphIndent.d.ts +6 -0
  193. package/dist/prosemirror/commands/clearParagraphIndent.js +25 -0
  194. package/dist/prosemirror/commands/pageBreak.js +15 -1
  195. package/dist/prosemirror/commentIdAllocator.d.ts +15 -22
  196. package/dist/prosemirror/commentIdAllocator.js +50 -25
  197. package/dist/prosemirror/commentReferenceAttrs.d.ts +2 -1
  198. package/dist/prosemirror/conversion/fromProseDoc.js +91 -19
  199. package/dist/prosemirror/conversion/listRenderingDefinition.d.ts +6 -0
  200. package/dist/prosemirror/conversion/listRenderingDefinition.js +97 -0
  201. package/dist/prosemirror/conversion/toProseDoc.d.ts +4 -3
  202. package/dist/prosemirror/conversion/toProseDoc.js +51 -11
  203. package/dist/prosemirror/executeEditorCommand.d.ts +11 -0
  204. package/dist/prosemirror/executeEditorCommand.js +26 -0
  205. package/dist/prosemirror/extensions/StarterKit.js +2 -0
  206. package/dist/prosemirror/extensions/core/HistoryExtension.js +97 -5
  207. package/dist/prosemirror/extensions/core/ParagraphExtension.js +53 -20
  208. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +22 -28
  209. package/dist/prosemirror/extensions/features/ListExtension.js +18 -5
  210. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +5 -2
  211. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +13 -1
  212. package/dist/prosemirror/extensions/marks/TextColorExtension.js +3 -2
  213. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +2 -0
  214. package/dist/prosemirror/extensions/marks/markUtils.js +70 -8
  215. package/dist/prosemirror/extensions/nodes/NoteMarkerExtension.d.ts +6 -0
  216. package/dist/prosemirror/extensions/nodes/NoteMarkerExtension.js +36 -0
  217. package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.js +4 -2
  218. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +8 -6
  219. package/dist/prosemirror/listMarker.d.ts +1 -0
  220. package/dist/prosemirror/listMarker.js +16 -7
  221. package/dist/prosemirror/listNumbering.js +26 -5
  222. package/dist/prosemirror/listRenderingAttrs.d.ts +1 -0
  223. package/dist/prosemirror/listRenderingAttrs.js +3 -0
  224. package/dist/prosemirror/markupViewNotes.js +3 -2
  225. package/dist/prosemirror/moveRangeBoundaryAttrs.d.ts +2 -1
  226. package/dist/prosemirror/noteReferenceReview.js +5 -3
  227. package/dist/prosemirror/plugins/revisionIds.d.ts +23 -19
  228. package/dist/prosemirror/plugins/revisionIds.js +110 -29
  229. package/dist/prosemirror/plugins/suggestionMode.d.ts +2 -2
  230. package/dist/prosemirror/plugins/suggestionMode.js +246 -42
  231. package/dist/prosemirror/rangeAnchorAttrs.d.ts +2 -1
  232. package/dist/prosemirror/runFormattingFromMarks.d.ts +4 -1
  233. package/dist/prosemirror/runFormattingFromMarks.js +15 -1
  234. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +2 -1
  235. package/dist/prosemirror/runFormattingInlineCarriers.js +4 -1
  236. package/dist/prosemirror/schema/marks.d.ts +2 -0
  237. package/dist/prosemirror/schema/nodes.d.ts +10 -2
  238. package/dist/prosemirror/storyListNumbering.js +11 -9
  239. package/dist/prosemirror/textBoxAnchorAttrs.d.ts +2 -1
  240. package/dist/prosemirror/textInput.d.ts +6 -1
  241. package/dist/prosemirror/textInput.js +14 -3
  242. package/dist/prosemirror/trackedRevisionPath.js +2 -1
  243. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -0
  244. package/dist/prosemirror/trackedRunInlineAtoms.js +1 -0
  245. package/dist/prosemirror/utils/visualLineNavigation.js +6 -8
  246. package/dist/prosemirror/validation.js +4 -0
  247. package/dist/types/content.d.ts +2 -2
  248. package/dist/utils/createDocument.js +2 -2
  249. package/dist/utils/mergeDocumentContent.d.ts +2 -2
  250. package/dist/utils/mergeDocumentContent.js +11 -8
  251. package/package.json +14 -2
@@ -0,0 +1,168 @@
1
+ import { splitsSurrogatePair } from "../ai-edits/character-boundaries.js";
2
+ import { CanonicalSessionError } from "./canonicalSession.js";
3
+ import { Result } from "better-result";
4
+ //#region src/controller/canonicalComposition.ts
5
+ const COMPOSITION_FLUSH_DELAY_MS = 25;
6
+ /** Native IME owns the provisional view; the captured canonical projection stays unchanged. */
7
+ const createCanonicalComposition = (options) => {
8
+ let state = { type: "committed" };
9
+ let authorizedNativeState = null;
10
+ let timer;
11
+ const clearTimer = () => {
12
+ if (timer !== void 0) clearTimeout(timer);
13
+ timer = void 0;
14
+ };
15
+ const replacement = (baseline, proposed) => {
16
+ const { $from, $to } = baseline.selection;
17
+ if (!$from.parent.isTextblock || !$to.parent.isTextblock) return null;
18
+ if (!$from.sameParent($to)) {
19
+ const from = baseline.selection.from;
20
+ const to = baseline.selection.to;
21
+ const insertedSize = proposed.doc.content.size - (baseline.doc.content.size - (to - from));
22
+ if (insertedSize < 0 || from + insertedSize > proposed.doc.content.size) return null;
23
+ const text = proposed.doc.textBetween(from, from + insertedSize, "", "");
24
+ if (text.length !== insertedSize) return null;
25
+ if (!baseline.tr.insertText(text, from, to).doc.eq(proposed.doc)) return null;
26
+ return {
27
+ from,
28
+ to,
29
+ text,
30
+ semantic: "composition"
31
+ };
32
+ }
33
+ const start = $from.start();
34
+ const next = proposed.doc.nodeAt(start - 1);
35
+ if (!next || next.type !== $from.parent.type || next.content.size !== next.textContent.length) return null;
36
+ const before = $from.parent.textContent;
37
+ const after = next.textContent;
38
+ let from = 0;
39
+ while (from < before.length && from < after.length && before[from] === after[from]) from++;
40
+ while (splitsSurrogatePair(before, from) || splitsSurrogatePair(after, from)) from--;
41
+ let oldEnd = before.length;
42
+ let newEnd = after.length;
43
+ while (oldEnd > from && newEnd > from && before[oldEnd - 1] === after[newEnd - 1]) {
44
+ oldEnd--;
45
+ newEnd--;
46
+ }
47
+ while (splitsSurrogatePair(before, oldEnd) || splitsSurrogatePair(after, newEnd)) {
48
+ oldEnd++;
49
+ newEnd++;
50
+ }
51
+ const text = after.slice(from, newEnd);
52
+ if (!baseline.tr.insertText(text, start + from, start + oldEnd).doc.eq(proposed.doc)) return null;
53
+ return {
54
+ from: start + from,
55
+ to: start + oldEnd,
56
+ text,
57
+ semantic: "composition"
58
+ };
59
+ };
60
+ const finish = (view, commit) => {
61
+ clearTimer();
62
+ authorizedNativeState = null;
63
+ const pending = state;
64
+ if (pending.type === "committed") return;
65
+ const input = commit && pending.type === "provisional" ? replacement(pending.baseline, view.state) : null;
66
+ state = { type: "committed" };
67
+ if (!view.isDestroyed) view.updateState(pending.baseline);
68
+ options.end();
69
+ if (!commit || view.isDestroyed || pending.type === "refused") return;
70
+ if (input?.from === input?.to && input?.text === "") return;
71
+ if (!input) {
72
+ options.refuse("Composition changed unsupported content; the canonical document was restored.");
73
+ return;
74
+ }
75
+ options.replace(input);
76
+ };
77
+ const schedule = (view) => {
78
+ clearTimer();
79
+ timer = setTimeout(() => finish(view, true), COMPOSITION_FLUSH_DELAY_MS);
80
+ };
81
+ return {
82
+ get status() {
83
+ return state.type;
84
+ },
85
+ get active() {
86
+ return state.type !== "committed";
87
+ },
88
+ start: (view) => {
89
+ if (state.type !== "committed") return;
90
+ if (!options.begin()) return;
91
+ state = {
92
+ type: "provisional",
93
+ baseline: view.state,
94
+ phase: "active"
95
+ };
96
+ },
97
+ ended: (view) => {
98
+ if (state.type === "committed") return;
99
+ state = {
100
+ type: state.type,
101
+ baseline: state.baseline,
102
+ phase: "ended"
103
+ };
104
+ schedule(view);
105
+ },
106
+ authorizeNative: (view) => {
107
+ authorizedNativeState = view.state;
108
+ },
109
+ flushed: (view) => {
110
+ const authorized = authorizedNativeState;
111
+ queueMicrotask(() => {
112
+ if (authorizedNativeState === authorized) authorizedNativeState = null;
113
+ });
114
+ if (state.type !== "committed" && state.phase === "ended") schedule(view);
115
+ },
116
+ recover: (view) => finish(view, true),
117
+ cancel: (view) => finish(view, false),
118
+ reset: () => {
119
+ clearTimer();
120
+ authorizedNativeState = null;
121
+ if (state.type !== "committed") options.end();
122
+ state = { type: "committed" };
123
+ },
124
+ accept: (view, transaction) => {
125
+ if (state.type === "committed") return false;
126
+ if (state.type === "refused") {
127
+ view.updateState(state.baseline);
128
+ return true;
129
+ }
130
+ if (typeof transaction.getMeta("composition") !== "number" && authorizedNativeState !== view.state) {
131
+ options.refuse("Other edits must wait until composition finishes.");
132
+ return true;
133
+ }
134
+ authorizedNativeState = null;
135
+ const applied = Result.try({
136
+ try: () => view.state.applyTransaction(transaction),
137
+ catch: (cause) => new CanonicalSessionError({
138
+ reason: "refused",
139
+ message: `Composition projection failed: ${String(cause)}`
140
+ })
141
+ });
142
+ if (applied.isErr()) {
143
+ view.updateState(state.baseline);
144
+ state = {
145
+ type: "refused",
146
+ baseline: state.baseline,
147
+ phase: state.phase
148
+ };
149
+ options.refuse(applied.error.message);
150
+ return true;
151
+ }
152
+ if (!replacement(state.baseline, applied.value.state)) {
153
+ view.updateState(state.baseline);
154
+ state = {
155
+ type: "refused",
156
+ baseline: state.baseline,
157
+ phase: state.phase
158
+ };
159
+ options.refuse("Composition changed unsupported content; the canonical document was restored.");
160
+ return true;
161
+ }
162
+ view.updateState(applied.value.state);
163
+ return true;
164
+ }
165
+ };
166
+ };
167
+ //#endregion
168
+ export { createCanonicalComposition };
@@ -0,0 +1,45 @@
1
+ import { EditorState, Transaction } from "prosemirror-state";
2
+ import { EditorView } from "prosemirror-view";
3
+ //#region src/controller/canonicalInput.d.ts
4
+ type ReplaceTextInput = {
5
+ from: number;
6
+ to: number;
7
+ text: string;
8
+ semantic?: "typing" | "deleteBackward" | "deleteForward" | "composition";
9
+ };
10
+ type CanonicalInputOptions = {
11
+ replace: (input: ReplaceTextInput) => void;
12
+ structure?: (type: "split" | "joinBackward" | "joinForward" | "lineBreak" | "pageBreak" | "tab" | "indent" | "outdent") => void;
13
+ command?: (name: "toggleBold" | "toggleItalic" | "toggleUnderline") => void;
14
+ breakUndoGroup?: () => void;
15
+ beginComposition?: () => boolean;
16
+ endComposition?: () => void;
17
+ undo: () => boolean;
18
+ redo: () => boolean;
19
+ refuse: (reason: string) => void;
20
+ };
21
+ /** Native changes require a preceding, classified input event at the same state. */
22
+ declare const createCanonicalInputBoundary: (options: CanonicalInputOptions) => {
23
+ reset: () => void;
24
+ readonly isComposing: boolean;
25
+ acceptComposition: (view: EditorView, transaction: Transaction) => boolean;
26
+ handleTextInput: (view: EditorView, from: number, to: number, text: string) => boolean;
27
+ handleKeyDown: (view: EditorView, event: KeyboardEvent) => boolean;
28
+ handleDOMEvents: {
29
+ compositionstart: (view: EditorView) => boolean;
30
+ compositionend: (view: EditorView) => boolean;
31
+ mousedown: (view: EditorView) => boolean;
32
+ blur: (view: EditorView) => boolean;
33
+ beforeinput: (view: EditorView, event: InputEvent) => boolean;
34
+ input: (view: EditorView) => boolean;
35
+ paste: (_view: EditorView, event: Event) => boolean;
36
+ cut: (_view: EditorView, event: Event) => boolean;
37
+ drop: (_view: EditorView, event: Event) => boolean;
38
+ };
39
+ /** Returns a classified intent only; the proposed PM document is never authoritative. */
40
+ takeNativeProposal: (state: EditorState, transaction: Transaction) => ReplaceTextInput | null;
41
+ refuseNativeMutation: (view: EditorView) => void;
42
+ commitNativeProposal: (view: EditorView, transaction: Transaction) => boolean;
43
+ };
44
+ //#endregion
45
+ export { createCanonicalInputBoundary };
@@ -0,0 +1,338 @@
1
+ import { handleEditorBeforeInput } from "../prosemirror/textInput.js";
2
+ import { createCanonicalComposition } from "./canonicalComposition.js";
3
+ import { deletionRange, isCanonicalJoinBoundary } from "./canonicalSession.js";
4
+ import { ReplaceStep } from "prosemirror-transform";
5
+ import { Selection, TextSelection } from "prosemirror-state";
6
+ //#region src/controller/canonicalInput.ts
7
+ /** Native changes require a preceding, classified input event at the same state. */
8
+ const createCanonicalInputBoundary = (options) => {
9
+ const composition = createCanonicalComposition({
10
+ begin: () => options.beginComposition?.() ?? true,
11
+ end: () => options.endComposition?.(),
12
+ replace: options.replace,
13
+ refuse: options.refuse
14
+ });
15
+ let proposal = { type: "idle" };
16
+ let authorizedInput = null;
17
+ let groupClosed = false;
18
+ const beginGesture = () => {
19
+ proposal = { type: "idle" };
20
+ authorizedInput = null;
21
+ groupClosed = false;
22
+ };
23
+ const closeGroup = () => {
24
+ if (groupClosed) return;
25
+ groupClosed = true;
26
+ options.breakUndoGroup?.();
27
+ };
28
+ const repaint = (view) => {
29
+ queueMicrotask(() => {
30
+ if (!view.isDestroyed) view.updateState(view.state);
31
+ });
32
+ };
33
+ const refuseEvent = (event, reason) => {
34
+ event.preventDefault();
35
+ closeGroup();
36
+ if (!composition.active && proposal.type !== "refused") options.refuse(reason);
37
+ if (!composition.active) proposal = { type: "refused" };
38
+ return true;
39
+ };
40
+ const refuseNativeMutation = (view) => {
41
+ if (!composition.active && proposal.type !== "refused") {
42
+ options.refuse("Unclassified native text is unavailable in this session.");
43
+ proposal = { type: "refused" };
44
+ }
45
+ repaint(view);
46
+ };
47
+ const takeNativeProposal = (state, transaction) => {
48
+ const pending = proposal;
49
+ if (pending.type !== "replacement") return null;
50
+ proposal = { type: "idle" };
51
+ if (pending.state !== state || transaction.steps.length !== 1) return null;
52
+ const step = transaction.steps.at(0);
53
+ if (!(step instanceof ReplaceStep) || "structure" in step && step.structure === true || step.from !== pending.input.from || step.to !== pending.input.to) return null;
54
+ if (step.slice.openStart !== 0 || step.slice.openEnd !== 0) return null;
55
+ let text = "";
56
+ let plain = true;
57
+ step.slice.content.forEach((node) => {
58
+ if (!node.isText) plain = false;
59
+ text += node.text ?? "";
60
+ });
61
+ if (!plain || text !== pending.input.text) return null;
62
+ return state.tr.insertText(text, step.from, step.to).doc.eq(transaction.doc) ? pending.input : null;
63
+ };
64
+ return {
65
+ reset: () => {
66
+ proposal = { type: "idle" };
67
+ authorizedInput = null;
68
+ composition.reset();
69
+ },
70
+ get isComposing() {
71
+ return composition.active;
72
+ },
73
+ acceptComposition: composition.accept,
74
+ handleTextInput: (view, from, to, text) => {
75
+ if (composition.active) return false;
76
+ const pending = proposal;
77
+ if (authorizedInput?.view === view && authorizedInput.state === view.state) {
78
+ options.replace({
79
+ from,
80
+ to,
81
+ text,
82
+ semantic: "typing"
83
+ });
84
+ return true;
85
+ }
86
+ if (pending.type === "replacement" && pending.state === view.state && pending.input.from === from && pending.input.to === to && pending.input.text === text) {
87
+ proposal = { type: "idle" };
88
+ options.replace(pending.input);
89
+ return true;
90
+ }
91
+ refuseNativeMutation(view);
92
+ return true;
93
+ },
94
+ handleKeyDown: (view, event) => {
95
+ beginGesture();
96
+ if (composition.active) {
97
+ if (event.key === "Escape") {
98
+ event.preventDefault();
99
+ composition.cancel(view);
100
+ return true;
101
+ }
102
+ if (event.metaKey || event.ctrlKey) {
103
+ event.preventDefault();
104
+ return true;
105
+ }
106
+ return false;
107
+ }
108
+ const modifier = event.metaKey || event.ctrlKey;
109
+ if (modifier || event.altKey || /^(?:Arrow|Home$|End$|Page|Escape$|Tab$)/u.test(event.key)) closeGroup();
110
+ if (modifier && event.key.toLowerCase() === "z") {
111
+ event.preventDefault();
112
+ if (event.shiftKey) options.redo();
113
+ else options.undo();
114
+ return true;
115
+ }
116
+ if (modifier && event.key.toLowerCase() === "y") {
117
+ event.preventDefault();
118
+ options.redo();
119
+ return true;
120
+ }
121
+ if (modifier && event.key.toLowerCase() === "a") {
122
+ event.preventDefault();
123
+ view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, Selection.atStart(view.state.doc).from, Selection.atEnd(view.state.doc).to)));
124
+ return true;
125
+ }
126
+ if (modifier && (event.key.toLowerCase() === "b" || event.key.toLowerCase() === "i" || event.key.toLowerCase() === "u") && options.command !== void 0) {
127
+ event.preventDefault();
128
+ switch (event.key.toLowerCase()) {
129
+ case "b":
130
+ options.command("toggleBold");
131
+ break;
132
+ case "i":
133
+ options.command("toggleItalic");
134
+ break;
135
+ case "u":
136
+ options.command("toggleUnderline");
137
+ break;
138
+ }
139
+ return true;
140
+ }
141
+ if (event.key === "Enter") {
142
+ if (view.composing) return refuseEvent(event, "Paragraph structure edits are unavailable during composition.");
143
+ event.preventDefault();
144
+ closeGroup();
145
+ if (options.structure === void 0) return refuseEvent(event, "Paragraph structure edits are unavailable in this session.");
146
+ if (modifier) options.structure("pageBreak");
147
+ else options.structure(event.shiftKey ? "lineBreak" : "split");
148
+ return true;
149
+ }
150
+ if (event.key === "Tab" && !modifier && !event.altKey && options.structure !== void 0) {
151
+ event.preventDefault();
152
+ if (view.state.selection.$from.parent.attrs["numPr"]?.kind === "reference") options.structure(event.shiftKey ? "outdent" : "indent");
153
+ else if (!event.shiftKey) options.structure("tab");
154
+ return true;
155
+ }
156
+ if (event.key !== "Backspace" && event.key !== "Delete") return false;
157
+ event.preventDefault();
158
+ if (modifier || event.altKey || view.composing || composition.active) {
159
+ closeGroup();
160
+ if (!composition.active) options.refuse("Only plain character deletion is available in this session.");
161
+ return true;
162
+ }
163
+ if (view.state.selection.empty && options.structure !== void 0 && isCanonicalJoinBoundary(view.state, event.key === "Backspace" ? "backward" : "forward")) {
164
+ closeGroup();
165
+ options.structure(event.key === "Backspace" ? "joinBackward" : "joinForward");
166
+ return true;
167
+ }
168
+ const range = deletionRange(view.state, event.key === "Backspace" ? "backward" : "forward");
169
+ if (range.isErr()) {
170
+ closeGroup();
171
+ options.refuse(range.error.message);
172
+ } else options.replace({
173
+ ...range.value,
174
+ text: "",
175
+ semantic: event.key === "Backspace" ? "deleteBackward" : "deleteForward"
176
+ });
177
+ return true;
178
+ },
179
+ handleDOMEvents: {
180
+ compositionstart: (view) => {
181
+ beginGesture();
182
+ composition.start(view);
183
+ return false;
184
+ },
185
+ compositionend: (view) => {
186
+ composition.ended(view);
187
+ return false;
188
+ },
189
+ mousedown: (view) => {
190
+ composition.recover(view);
191
+ beginGesture();
192
+ closeGroup();
193
+ return false;
194
+ },
195
+ blur: (view) => {
196
+ composition.recover(view);
197
+ beginGesture();
198
+ closeGroup();
199
+ repaint(view);
200
+ return false;
201
+ },
202
+ beforeinput: (view, event) => {
203
+ beginGesture();
204
+ if (event.isComposing || event.inputType === "insertCompositionText" || event.inputType === "insertFromComposition" || event.inputType === "deleteCompositionText" || event.inputType === "deleteByComposition") {
205
+ if (composition.active) {
206
+ composition.authorizeNative(view);
207
+ return false;
208
+ }
209
+ return refuseEvent(event, "Composition has no captured canonical baseline.");
210
+ }
211
+ if (composition.active) {
212
+ const refusedNativeCommit = composition.status === "refused" && view.composing;
213
+ composition.recover(view);
214
+ proposal = { type: "idle" };
215
+ if (refusedNativeCommit) {
216
+ event.preventDefault();
217
+ return true;
218
+ }
219
+ }
220
+ if (event.inputType === "insertText" || event.inputType === "insertReplacementText") {
221
+ if (event.cancelable) {
222
+ if (view.composing && event.inputType === "insertText" && event.data !== null) {
223
+ event.preventDefault();
224
+ proposal = { type: "idle" };
225
+ options.replace({
226
+ from: view.state.selection.from,
227
+ to: view.state.selection.to,
228
+ text: event.data,
229
+ semantic: "typing"
230
+ });
231
+ return true;
232
+ }
233
+ proposal = { type: "idle" };
234
+ authorizedInput = {
235
+ view,
236
+ state: view.state
237
+ };
238
+ try {
239
+ if (handleEditorBeforeInput(view, event)) return true;
240
+ return refuseEvent(event, "This native replacement cannot be addressed safely.");
241
+ } finally {
242
+ authorizedInput = null;
243
+ }
244
+ }
245
+ if (event.inputType !== "insertText" || event.data === null) return refuseEvent(event, "This native replacement cannot be addressed safely.");
246
+ proposal = {
247
+ type: "replacement",
248
+ state: view.state,
249
+ input: {
250
+ from: view.state.selection.from,
251
+ to: view.state.selection.to,
252
+ text: event.data,
253
+ semantic: "typing"
254
+ }
255
+ };
256
+ return false;
257
+ }
258
+ if ((event.inputType === "insertParagraph" || event.inputType === "insertLineBreak") && event.cancelable && options.structure !== void 0) {
259
+ event.preventDefault();
260
+ closeGroup();
261
+ options.structure(event.inputType === "insertParagraph" ? "split" : "lineBreak");
262
+ return true;
263
+ }
264
+ if (event.inputType === "deleteContentBackward" || event.inputType === "deleteContentForward") {
265
+ const selection = view.state.selection;
266
+ const backward = event.inputType === "deleteContentBackward";
267
+ if (event.cancelable && selection.empty && options.structure !== void 0 && isCanonicalJoinBoundary(view.state, backward ? "backward" : "forward")) {
268
+ event.preventDefault();
269
+ closeGroup();
270
+ options.structure(backward ? "joinBackward" : "joinForward");
271
+ return true;
272
+ }
273
+ const range = deletionRange(view.state, event.inputType === "deleteContentBackward" ? "backward" : "forward");
274
+ if (range.isErr()) return refuseEvent(event, range.error.message);
275
+ const input = {
276
+ ...range.value,
277
+ text: "",
278
+ semantic: event.inputType === "deleteContentBackward" ? "deleteBackward" : "deleteForward"
279
+ };
280
+ if (event.cancelable) {
281
+ event.preventDefault();
282
+ options.replace(input);
283
+ return true;
284
+ }
285
+ proposal = {
286
+ type: "replacement",
287
+ state: view.state,
288
+ input
289
+ };
290
+ return false;
291
+ }
292
+ if (event.inputType === "historyUndo" || event.inputType === "historyRedo") {
293
+ event.preventDefault();
294
+ if (event.inputType === "historyUndo") options.undo();
295
+ else options.redo();
296
+ return true;
297
+ }
298
+ return refuseEvent(event, `Input ${event.inputType} is unavailable in this session.`);
299
+ },
300
+ input: (view) => {
301
+ if (composition.active) {
302
+ composition.flushed(view);
303
+ return false;
304
+ }
305
+ const pending = proposal;
306
+ queueMicrotask(() => {
307
+ if (view.isDestroyed) return;
308
+ view.updateState(view.state);
309
+ if (proposal === pending && !composition.active) proposal = { type: "idle" };
310
+ });
311
+ return false;
312
+ },
313
+ paste: (_view, event) => {
314
+ beginGesture();
315
+ return refuseEvent(event, "Paste is unavailable in this session.");
316
+ },
317
+ cut: (_view, event) => {
318
+ beginGesture();
319
+ return refuseEvent(event, "Cut is unavailable in this session.");
320
+ },
321
+ drop: (_view, event) => {
322
+ beginGesture();
323
+ return refuseEvent(event, "Drop is unavailable in this session.");
324
+ }
325
+ },
326
+ /** Returns a classified intent only; the proposed PM document is never authoritative. */
327
+ takeNativeProposal,
328
+ refuseNativeMutation,
329
+ commitNativeProposal: (view, transaction) => {
330
+ const input = takeNativeProposal(view.state, transaction);
331
+ if (!input) return false;
332
+ options.replace(input);
333
+ return true;
334
+ }
335
+ };
336
+ };
337
+ //#endregion
338
+ export { createCanonicalInputBoundary };
@@ -0,0 +1,22 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ import { CreateHeaderFooterOp, DOCUMENT_OP_TYPES, DocumentOp, FormattingPatch, FormattingPatch as FormattingPatch$1, OpStory, RemoveHeaderFooterOp, SetSectionPropsOp, TextPosition } from "@stll/docx-core/ops";
3
+ //#region src/controller/canonicalOperations.d.ts
4
+ declare const canonicalFinalSectionIndex: (document: document_d_exports.Document) => number;
5
+ declare const withCanonicalParagraphIds: (content: document_d_exports.BlockContent[], document: document_d_exports.Document) => document_d_exports.BlockContent[];
6
+ type CreateCanonicalHeaderFooterOptions = {
7
+ document: document_d_exports.Document;
8
+ position: "header" | "footer";
9
+ referenceType: document_d_exports.HeaderFooterType;
10
+ sectionIndex?: number;
11
+ };
12
+ declare const createCanonicalHeaderFooterOperation: ({ document, position, referenceType, sectionIndex }: CreateCanonicalHeaderFooterOptions) => CreateHeaderFooterOp;
13
+ type RemoveCanonicalHeaderFooterOptions = {
14
+ document: document_d_exports.Document;
15
+ position: "header" | "footer";
16
+ rId: string;
17
+ };
18
+ /** Remove each explicit binding, leaving inherited sections to inherit the remaining parts. */
19
+ declare const removeCanonicalHeaderFooterOperations: ({ document, position, rId }: RemoveCanonicalHeaderFooterOptions) => RemoveHeaderFooterOp[];
20
+ declare const createCanonicalSectionPropertiesOperation: (document: document_d_exports.Document, patch: FormattingPatch$1<document_d_exports.SectionProperties>) => SetSectionPropsOp;
21
+ //#endregion
22
+ export { DOCUMENT_OP_TYPES, type DocumentOp, type FormattingPatch, type OpStory, type TextPosition, canonicalFinalSectionIndex, createCanonicalHeaderFooterOperation, createCanonicalSectionPropertiesOperation, removeCanonicalHeaderFooterOperations, withCanonicalParagraphIds };
@@ -0,0 +1,85 @@
1
+ import { visitDocxParagraphs } from "../docx/paragraphTraversal.js";
2
+ import { generateHexId, isValidHexId } from "../utils/hexId.js";
3
+ import { DOCUMENT_OP_TYPES, DOCUMENT_OP_TYPES as DOCUMENT_OP_TYPES$1 } from "@stll/docx-core/ops";
4
+ //#region src/controller/canonicalOperations.ts
5
+ /** Adapter command boundaries allocate identities; journaled operations remain deterministic. */
6
+ const canonicalFinalSectionIndex = (document) => document.package.document.content.filter((block) => block.type === "paragraph" && block.sectionProperties !== void 0).length;
7
+ const withCanonicalParagraphIds = (content, document) => {
8
+ const taken = /* @__PURE__ */ new Set();
9
+ const { document: documentBody, headers, footers, footnotes, endnotes } = document.package;
10
+ visitDocxParagraphs({
11
+ documentBody,
12
+ headers,
13
+ footers,
14
+ footnotes,
15
+ endnotes
16
+ }, (paragraph) => {
17
+ if (paragraph.paraId) taken.add(paragraph.paraId.toUpperCase());
18
+ });
19
+ const owned = structuredClone(content);
20
+ visitDocxParagraphs({ documentBody: { content: owned } }, (paragraph) => {
21
+ if (paragraph.paraId && isValidHexId(paragraph.paraId) && Number.parseInt(paragraph.paraId, 16) > 0 && Number.parseInt(paragraph.paraId, 16) < 2147483647 && !taken.has(paragraph.paraId.toUpperCase())) {
22
+ taken.add(paragraph.paraId.toUpperCase());
23
+ return;
24
+ }
25
+ let paraId = generateHexId();
26
+ while (taken.has(paraId)) paraId = generateHexId();
27
+ taken.add(paraId);
28
+ paragraph.paraId = paraId;
29
+ });
30
+ return owned;
31
+ };
32
+ const createCanonicalHeaderFooterOperation = ({ document, position, referenceType, sectionIndex = canonicalFinalSectionIndex(document) }) => {
33
+ const occupied = /* @__PURE__ */ new Set([
34
+ ...document.package.headers?.keys() ?? [],
35
+ ...document.package.footers?.keys() ?? [],
36
+ ...document.package.relationships?.keys() ?? []
37
+ ]);
38
+ const base = `rId_new_${position}_${referenceType}`;
39
+ let rId = base;
40
+ let suffix = 2;
41
+ while (occupied.has(rId)) rId = `${base}_${suffix++}`;
42
+ return {
43
+ type: DOCUMENT_OP_TYPES$1.CREATE_HEADER_FOOTER,
44
+ sectionIndex,
45
+ story: {
46
+ kind: position,
47
+ rId
48
+ },
49
+ referenceType,
50
+ content: withCanonicalParagraphIds([{
51
+ type: "paragraph",
52
+ content: []
53
+ }], document)
54
+ };
55
+ };
56
+ /** Remove each explicit binding, leaving inherited sections to inherit the remaining parts. */
57
+ const removeCanonicalHeaderFooterOperations = ({ document, position, rId }) => {
58
+ const properties = [];
59
+ for (const block of document.package.document.content) if (block.type === "paragraph" && block.sectionProperties) properties.push(block.sectionProperties);
60
+ properties.push(document.package.document.finalSectionProperties ?? {});
61
+ const operations = [];
62
+ for (const [sectionIndex, section] of properties.entries()) {
63
+ const references = position === "header" ? section.headerReferences : section.footerReferences;
64
+ for (const reference of references ?? []) {
65
+ if (reference.rId !== rId) continue;
66
+ operations.push({
67
+ type: DOCUMENT_OP_TYPES$1.REMOVE_HEADER_FOOTER,
68
+ sectionIndex,
69
+ story: {
70
+ kind: position,
71
+ rId
72
+ },
73
+ referenceType: reference.type
74
+ });
75
+ }
76
+ }
77
+ return operations;
78
+ };
79
+ const createCanonicalSectionPropertiesOperation = (document, patch) => ({
80
+ type: DOCUMENT_OP_TYPES$1.SET_SECTION_PROPS,
81
+ sectionIndex: canonicalFinalSectionIndex(document),
82
+ patch
83
+ });
84
+ //#endregion
85
+ export { DOCUMENT_OP_TYPES, canonicalFinalSectionIndex, createCanonicalHeaderFooterOperation, createCanonicalSectionPropertiesOperation, removeCanonicalHeaderFooterOperations, withCanonicalParagraphIds };
@@ -0,0 +1,12 @@
1
+ import { FolioEditor } from "./folioEditor.js";
2
+ //#region src/controller/canonicalReview.d.ts
3
+ type ResolveCanonicalReviewRangeOptions = {
4
+ editor: Pick<FolioEditor, "getCanonicalDocument" | "getState" | "resolveCanonicalRevisions"> | null | undefined;
5
+ from: number;
6
+ to: number;
7
+ resolution: "accept" | "reject";
8
+ };
9
+ /** Null delegates to the default session's ProseMirror review commands. */
10
+ declare const resolveCanonicalReviewRange: ({ editor, from, to, resolution }: ResolveCanonicalReviewRangeOptions) => boolean | null;
11
+ //#endregion
12
+ export { resolveCanonicalReviewRange };