@portone/docx-editor 0.2.0 → 0.3.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 (256) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/CONTRIBUTING.md +9 -1
  3. package/dist/DocxEditor.d.ts +6 -0
  4. package/dist/DocxEditor.js +12 -29
  5. package/dist/core.d.ts +23 -8
  6. package/dist/core.js +9 -4
  7. package/dist/docx/cloning.d.ts +38 -0
  8. package/dist/docx/cloning.js +74 -0
  9. package/dist/docx/commentOnlyChange.d.ts +14 -7
  10. package/dist/docx/commentOnlyChange.js +9 -139
  11. package/dist/docx/comments/constants.d.ts +3 -4
  12. package/dist/docx/comments/constants.js +4 -5
  13. package/dist/docx/comments/grammar.d.ts +86 -0
  14. package/dist/docx/comments/grammar.js +223 -0
  15. package/dist/docx/comments/parts.d.ts +52 -0
  16. package/dist/docx/comments/parts.js +279 -0
  17. package/dist/docx/comments/people.d.ts +11 -2
  18. package/dist/docx/comments/people.js +28 -106
  19. package/dist/docx/comments/policy.d.ts +7 -0
  20. package/dist/docx/comments/policy.js +43 -0
  21. package/dist/docx/comments/reading.d.ts +5 -3
  22. package/dist/docx/comments/reading.js +30 -29
  23. package/dist/docx/comments/writing.d.ts +19 -7
  24. package/dist/docx/comments/writing.js +105 -137
  25. package/dist/docx/documentSettings.d.ts +7 -0
  26. package/dist/docx/documentSettings.js +10 -2
  27. package/dist/docx/exportDocx.d.ts +40 -5
  28. package/dist/docx/exportDocx.js +71 -72
  29. package/dist/docx/exportRefs.d.ts +5 -2
  30. package/dist/docx/exportRefs.js +3 -1
  31. package/dist/docx/fidelity.d.ts +43 -0
  32. package/dist/docx/fidelity.js +64 -0
  33. package/dist/docx/formatting/attrs.d.ts +27 -0
  34. package/dist/docx/formatting/attrs.js +31 -0
  35. package/dist/docx/formatting/context.d.ts +31 -0
  36. package/dist/docx/formatting/context.js +42 -0
  37. package/dist/docx/formatting/direct.d.ts +6 -5
  38. package/dist/docx/formatting/direct.js +52 -81
  39. package/dist/docx/formatting/resolve.d.ts +45 -0
  40. package/dist/docx/formatting/resolve.js +140 -0
  41. package/dist/docx/formatting/runProperties.d.ts +93 -0
  42. package/dist/docx/formatting/runProperties.js +316 -0
  43. package/dist/docx/formatting/styles.js +3 -3
  44. package/dist/docx/formatting/tabStops.js +7 -22
  45. package/dist/docx/formatting.d.ts +4 -1
  46. package/dist/docx/formatting.js +4 -1
  47. package/dist/docx/headersFooters.js +13 -22
  48. package/dist/docx/identities.d.ts +67 -0
  49. package/dist/docx/identities.js +174 -0
  50. package/dist/docx/importDocx.d.ts +12 -1
  51. package/dist/docx/importDocx.js +72 -79
  52. package/dist/docx/importParagraph.d.ts +1 -1
  53. package/dist/docx/importParagraph.js +4 -3
  54. package/dist/docx/importTable.d.ts +1 -1
  55. package/dist/docx/importTable.js +17 -1
  56. package/dist/docx/invariants.d.ts +33 -0
  57. package/dist/docx/invariants.js +256 -0
  58. package/dist/docx/media.d.ts +6 -4
  59. package/dist/docx/media.js +6 -37
  60. package/dist/docx/newLists.d.ts +20 -0
  61. package/dist/docx/newLists.js +36 -0
  62. package/dist/docx/notes.js +6 -12
  63. package/dist/docx/numberingPlanner.d.ts +8 -0
  64. package/dist/docx/numberingPlanner.js +19 -0
  65. package/dist/docx/packageParts.d.ts +42 -0
  66. package/dist/docx/packageParts.js +135 -0
  67. package/dist/docx/pageGeometry.d.ts +2 -0
  68. package/dist/docx/pageGeometry.js +18 -9
  69. package/dist/docx/paraProps.d.ts +10 -22
  70. package/dist/docx/paraProps.js +75 -99
  71. package/dist/docx/partPlan.d.ts +36 -0
  72. package/dist/docx/partPlan.js +59 -0
  73. package/dist/docx/protectionPolicy.d.ts +127 -0
  74. package/dist/docx/protectionPolicy.js +169 -0
  75. package/dist/docx/relationships.d.ts +1 -1
  76. package/dist/docx/relationships.js +15 -15
  77. package/dist/docx/runProps.d.ts +9 -22
  78. package/dist/docx/runProps.js +15 -168
  79. package/dist/docx/scan.js +20 -52
  80. package/dist/docx/sdt.js +14 -37
  81. package/dist/docx/sdtProps.d.ts +8 -1
  82. package/dist/docx/sdtProps.js +10 -0
  83. package/dist/docx/serializeBlock.d.ts +2 -0
  84. package/dist/docx/serializeBlock.js +9 -7
  85. package/dist/docx/serializeParagraph.d.ts +2 -1
  86. package/dist/docx/serializeParagraph.js +25 -14
  87. package/dist/docx/serializeTable.js +63 -32
  88. package/dist/docx/session.d.ts +42 -14
  89. package/dist/docx/session.js +43 -17
  90. package/dist/docx/storyProjection.d.ts +36 -0
  91. package/dist/docx/storyProjection.js +23 -0
  92. package/dist/docx/tableFormatting/editing.js +130 -185
  93. package/dist/docx/tableFormatting/reading.d.ts +16 -6
  94. package/dist/docx/tableFormatting/reading.js +40 -20
  95. package/dist/docx/tableTemplate.js +37 -5
  96. package/dist/docx/theme.d.ts +0 -6
  97. package/dist/docx/theme.js +0 -8
  98. package/dist/download.d.ts +8 -5
  99. package/dist/download.js +2 -0
  100. package/dist/editor/clipboard/images.js +17 -3
  101. package/dist/editor/clipboard/inlineFormatting.d.ts +7 -0
  102. package/dist/editor/clipboard/inlineFormatting.js +20 -30
  103. package/dist/editor/commands/breakCommands.js +4 -3
  104. package/dist/editor/commands/canRunCommand.d.ts +2 -2
  105. package/dist/editor/commands/canRunCommand.js +1 -1
  106. package/dist/editor/commands/comments/editing.d.ts +6 -2
  107. package/dist/editor/commands/comments/editing.js +21 -36
  108. package/dist/editor/commands/exportQueries.d.ts +15 -0
  109. package/dist/editor/commands/exportQueries.js +14 -0
  110. package/dist/editor/commands/fidelityQueries.d.ts +12 -0
  111. package/dist/editor/commands/fidelityQueries.js +8 -0
  112. package/dist/editor/commands/formatting/editing.d.ts +2 -2
  113. package/dist/editor/commands/formatting/editing.js +26 -108
  114. package/dist/editor/commands/formatting/propertyCommands.d.ts +21 -0
  115. package/dist/editor/commands/formatting/propertyCommands.js +103 -0
  116. package/dist/editor/commands/formatting/shared.d.ts +3 -3
  117. package/dist/editor/commands/formatting/shared.js +5 -2
  118. package/dist/editor/commands/historyCommands.js +2 -1
  119. package/dist/editor/commands/indentCommands.js +5 -4
  120. package/dist/editor/commands/index.d.ts +11 -0
  121. package/dist/editor/commands/index.js +5 -0
  122. package/dist/editor/commands/linkCommands.js +5 -6
  123. package/dist/editor/commands/listCommands.js +8 -10
  124. package/dist/editor/commands/lockCommands.d.ts +7 -1
  125. package/dist/editor/commands/lockCommands.js +35 -37
  126. package/dist/editor/commands/paragraphCommands.js +28 -31
  127. package/dist/editor/commands/spacingCommands.js +1 -1
  128. package/dist/editor/commands/tabCommands.js +4 -3
  129. package/dist/editor/createEditor.d.ts +17 -29
  130. package/dist/editor/createEditor.js +61 -67
  131. package/dist/editor/documentStyles.d.ts +11 -35
  132. package/dist/editor/documentStyles.js +9 -52
  133. package/dist/editor/editorDocument.d.ts +56 -0
  134. package/dist/editor/editorDocument.js +74 -0
  135. package/dist/editor/externalClipboard.js +166 -33
  136. package/dist/editor/insertImage.js +4 -3
  137. package/dist/editor/insertTable.js +4 -3
  138. package/dist/editor/paragraphEdits.d.ts +11 -18
  139. package/dist/editor/paragraphEdits.js +7 -16
  140. package/dist/editor/plugins/displayDerivation.d.ts +47 -0
  141. package/dist/editor/plugins/displayDerivation.js +78 -0
  142. package/dist/editor/plugins/documentProtection.d.ts +3 -3
  143. package/dist/editor/plugins/imagePaste.js +4 -3
  144. package/dist/editor/plugins/keymap.js +18 -3
  145. package/dist/editor/plugins/lockedContent.d.ts +4 -4
  146. package/dist/editor/plugins/lockedContent.js +1 -1
  147. package/dist/editor/plugins/numberingDecorations.d.ts +5 -11
  148. package/dist/editor/plugins/numberingDecorations.js +7 -17
  149. package/dist/editor/plugins/paragraphDisplay.d.ts +7 -0
  150. package/dist/editor/plugins/paragraphDisplay.js +51 -0
  151. package/dist/editor/plugins/tabLayout.js +1 -1
  152. package/dist/editor/plugins/tableDisplay.d.ts +6 -0
  153. package/dist/editor/plugins/tableDisplay.js +16 -0
  154. package/dist/index.d.ts +2 -0
  155. package/dist/model/format.d.ts +30 -8
  156. package/dist/model/format.js +35 -22
  157. package/dist/model/tabStops.d.ts +9 -0
  158. package/dist/model/tabStops.js +18 -0
  159. package/dist/numbering/listTemplate.js +49 -10
  160. package/dist/numbering/parseNumbering.d.ts +14 -1
  161. package/dist/numbering/parseNumbering.js +35 -22
  162. package/dist/numbering/writeNumbering.d.ts +3 -4
  163. package/dist/numbering/writeNumbering.js +14 -25
  164. package/dist/ooxml/childOrder.d.ts +34 -0
  165. package/dist/ooxml/childOrder.js +496 -0
  166. package/dist/ooxml/element.d.ts +65 -0
  167. package/dist/ooxml/element.js +68 -0
  168. package/dist/ooxml/errors.d.ts +3 -2
  169. package/dist/ooxml/fragment.d.ts +53 -0
  170. package/dist/ooxml/fragment.js +76 -0
  171. package/dist/ooxml/image.d.ts +4 -0
  172. package/dist/ooxml/image.js +3 -5
  173. package/dist/ooxml/names.d.ts +27 -0
  174. package/dist/ooxml/names.js +29 -0
  175. package/dist/ooxml/partSplice.d.ts +67 -0
  176. package/dist/ooxml/partSplice.js +169 -0
  177. package/dist/ooxml/precedence.d.ts +31 -0
  178. package/dist/ooxml/precedence.js +51 -0
  179. package/dist/ooxml/props.d.ts +112 -0
  180. package/dist/ooxml/props.js +214 -0
  181. package/dist/ooxml/simpleTypes.d.ts +103 -0
  182. package/dist/ooxml/simpleTypes.js +182 -0
  183. package/dist/ooxml/tabStops.js +8 -27
  184. package/dist/ooxml/tagScan.d.ts +34 -0
  185. package/dist/ooxml/tagScan.js +108 -0
  186. package/dist/ooxml/units.d.ts +25 -13
  187. package/dist/ooxml/units.js +65 -31
  188. package/dist/ooxml/xml.d.ts +52 -0
  189. package/dist/ooxml/xml.js +64 -5
  190. package/dist/page/blockKinds.d.ts +103 -0
  191. package/dist/page/blockKinds.js +11 -0
  192. package/dist/page/kinds/index.d.ts +6 -0
  193. package/dist/page/kinds/index.js +10 -0
  194. package/dist/page/kinds/paragraphKind.d.ts +10 -0
  195. package/dist/page/kinds/paragraphKind.js +71 -0
  196. package/dist/page/kinds/tableKind.d.ts +10 -0
  197. package/dist/page/kinds/tableKind.js +178 -0
  198. package/dist/page/measureBlocks.d.ts +3 -1
  199. package/dist/page/measureBlocks.js +24 -25
  200. package/dist/page/pageDecorations.d.ts +27 -28
  201. package/dist/page/pageDecorations.js +70 -151
  202. package/dist/page/pageLayout.d.ts +10 -62
  203. package/dist/page/pageLayout.js +60 -53
  204. package/dist/page/usePageLayout.d.ts +2 -2
  205. package/dist/page/usePageLayout.js +3 -11
  206. package/dist/schema/attrRoles.d.ts +42 -0
  207. package/dist/schema/attrRoles.js +141 -0
  208. package/dist/schema/displayDerivation.d.ts +82 -0
  209. package/dist/schema/displayDerivation.js +130 -0
  210. package/dist/schema/docxSchema.d.ts +9 -0
  211. package/dist/schema/docxSchema.js +237 -114
  212. package/dist/schema/editGuard.d.ts +99 -0
  213. package/dist/schema/editGuard.js +43 -0
  214. package/dist/schema/guards.d.ts +91 -0
  215. package/dist/schema/guards.js +108 -0
  216. package/dist/schema/index.d.ts +2 -0
  217. package/dist/schema/index.js +2 -0
  218. package/dist/schema/locks.d.ts +17 -77
  219. package/dist/schema/locks.js +26 -70
  220. package/dist/schema/preservedGuards.d.ts +50 -0
  221. package/dist/schema/preservedGuards.js +80 -0
  222. package/dist/schema/protection.d.ts +17 -4
  223. package/dist/schema/protection.js +4 -2
  224. package/dist/schema/sourceEquality.d.ts +19 -0
  225. package/dist/schema/sourceEquality.js +39 -0
  226. package/dist/styles/inlineStyle.js +15 -6
  227. package/dist/table/cellFormatting.d.ts +8 -1
  228. package/dist/table/cellFormatting.js +10 -17
  229. package/dist/table/commands.d.ts +2 -2
  230. package/dist/table/commands.js +20 -18
  231. package/dist/table/format.d.ts +3 -0
  232. package/dist/table/format.js +3 -9
  233. package/dist/table/gridBorders.d.ts +14 -16
  234. package/dist/table/gridBorders.js +3 -41
  235. package/dist/table/index.d.ts +2 -2
  236. package/dist/table/merge.d.ts +4 -8
  237. package/dist/table/merge.js +5 -5
  238. package/package.json +10 -3
  239. package/dist/docx/comments/contentTypes.d.ts +0 -7
  240. package/dist/docx/comments/contentTypes.js +0 -38
  241. package/dist/docx/formatting/effectiveParagraph.d.ts +0 -15
  242. package/dist/docx/formatting/effectiveParagraph.js +0 -81
  243. package/dist/docx/propsXml.d.ts +0 -49
  244. package/dist/docx/propsXml.js +0 -302
  245. package/dist/docx/uniqueControls.d.ts +0 -14
  246. package/dist/docx/uniqueControls.js +0 -62
  247. package/dist/editor/plugins/bookmarkProtection.d.ts +0 -3
  248. package/dist/editor/plugins/bookmarkProtection.js +0 -28
  249. package/dist/editor/plugins/commentReservations.d.ts +0 -5
  250. package/dist/editor/plugins/commentReservations.js +0 -26
  251. package/dist/editor/plugins/noteProtection.d.ts +0 -3
  252. package/dist/editor/plugins/noteProtection.js +0 -25
  253. package/dist/editor/plugins/styledParagraphs.d.ts +0 -15
  254. package/dist/editor/plugins/styledParagraphs.js +0 -65
  255. package/dist/page/tableMeasurements.d.ts +0 -10
  256. package/dist/page/tableMeasurements.js +0 -115
@@ -11,9 +11,9 @@
11
11
  *
12
12
  * The protection is editor state rather than document state: it is the reader's standing, not the
13
13
  * file's, so nothing here is written to or read from OOXML. `editor/plugins/documentProtection`
14
- * holds it in the state and `./protectionState` reads it back; the guard (`transactionAllowed` in
15
- * `./locks`) asks the judgements here. They take documents alone, so that the `./core` entry can
16
- * make the same judgement over a file without an editor.
14
+ * holds it in the state and `./protectionState` reads it back; the guard list (`./guards`) asks the
15
+ * judgements here. They take documents alone, so that the `./core` entry can make the same
16
+ * judgement over a file without an editor.
17
17
  */
18
18
  import { type Node as PMNode } from "prosemirror-model";
19
19
  export type EditingProtection = "none" | "readOnly" | "comments";
@@ -31,7 +31,20 @@ export interface ProtectionState {
31
31
  }
32
32
  /** Whether this node is one of the three a comment stands in the story as */
33
33
  export declare function isCommentNode(node: PMNode): boolean;
34
- /** Whether the two documents differ in nothing but their comments */
34
+ /**
35
+ * The document with every comment taken out of it.
36
+ *
37
+ * `Fragment.fromArray` joins the text a marker had split, so a document a comment was put into
38
+ * reads the same as it did before the comment, and the two compare equal.
39
+ */
40
+ export declare function withoutComments(node: PMNode): PMNode;
41
+ /**
42
+ * Whether the two documents differ in nothing but their comments.
43
+ *
44
+ * The comparison is by source (`./sourceEquality`): a display value worked out again beside a
45
+ * comment is no content change, so it neither turns a comment edit into a body edit nor takes the
46
+ * ownership question off it.
47
+ */
35
48
  export declare function changesOnlyComments(before: PMNode, after: PMNode): boolean;
36
49
  /**
37
50
  * Whether a comment written under this identity may be edited or deleted.
@@ -1,5 +1,6 @@
1
1
  // src/schema/protection.ts
2
2
  import { Fragment } from "prosemirror-model";
3
+ import { sameSource } from "./sourceEquality.js";
3
4
  var COMMENT_NODES = /* @__PURE__ */ new Set([
4
5
  "commentStart",
5
6
  "commentEnd",
@@ -17,7 +18,7 @@ function withoutComments(node) {
17
18
  return node.copy(Fragment.fromArray(kept));
18
19
  }
19
20
  function changesOnlyComments(before, after) {
20
- return withoutComments(before).eq(withoutComments(after));
21
+ return sameSource(withoutComments(before), withoutComments(after));
21
22
  }
22
23
  function stringOrNull(value) {
23
24
  return typeof value === "string" ? value : null;
@@ -166,5 +167,6 @@ export {
166
167
  commentIdentitiesKept,
167
168
  commentOwned,
168
169
  isCommentNode,
169
- protectionAllows
170
+ protectionAllows,
171
+ withoutComments
170
172
  };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Whether two nodes would be written back as the same XML.
3
+ *
4
+ * `Node.eq` answers a different question: whether they are the same node, display values and all.
5
+ * That is the right question inside an editing session, where both sides came from one import and
6
+ * a difference can only be an edit. It is the wrong one across a re-derivation, because the values
7
+ * worked out from the surroundings (`./attrRoles`) move without the document moving, and a block
8
+ * judged changed is a block rewritten, which costs it the markup the writer does not model.
9
+ *
10
+ * So the comparison is `Node.eq` with the display attrs left out, and nothing else left out: the
11
+ * type, the text, the marks and the source and session attrs all have to agree, and the recursion
12
+ * runs the same way, so a node that says the same thing in a different shape is still a change.
13
+ */
14
+ import type { Node as PMNode } from "prosemirror-model";
15
+ /**
16
+ * Whether the two would write the same XML: the same type, text, marks and source attrs, all the
17
+ * way down. What the editor works out for the screen is not compared.
18
+ */
19
+ export declare function sameSource(a: PMNode, b: PMNode): boolean;
@@ -0,0 +1,39 @@
1
+ // src/schema/sourceEquality.ts
2
+ import { displayAttrsOf } from "./attrRoles.js";
3
+ function sameValue(a, b) {
4
+ if (a === b) return true;
5
+ if (Array.isArray(a) || Array.isArray(b)) {
6
+ return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((item, at) => sameValue(item, b[at]));
7
+ }
8
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
9
+ return false;
10
+ }
11
+ const left = Object.entries(a);
12
+ return left.length === Object.keys(b).length && left.every(
13
+ ([key, value]) => key in b && sameValue(value, b[key])
14
+ );
15
+ }
16
+ function sameAttrs(display, a, b) {
17
+ return Object.keys(a).filter((name) => !display.includes(name)).every((name) => sameValue(a[name], b[name]));
18
+ }
19
+ function sameMarks(a, b) {
20
+ return a.length === b.length && a.every((mark, at) => {
21
+ const other = b[at];
22
+ return other !== void 0 && mark.type === other.type && sameAttrs(displayAttrsOf(mark.type), mark.attrs, other.attrs);
23
+ });
24
+ }
25
+ function sameSource(a, b) {
26
+ if (a === b) return true;
27
+ if (a.type !== b.type || !sameMarks(a.marks, b.marks) || !sameAttrs(displayAttrsOf(a.type), a.attrs, b.attrs)) {
28
+ return false;
29
+ }
30
+ if (a.isText) return a.text === b.text;
31
+ if (a.childCount !== b.childCount) return false;
32
+ for (let at = 0; at < a.childCount; at += 1) {
33
+ if (!sameSource(a.child(at), b.child(at))) return false;
34
+ }
35
+ return true;
36
+ }
37
+ export {
38
+ sameSource
39
+ };
@@ -59,20 +59,29 @@ function lineHeightValue(spacing) {
59
59
  return `max(${pt(spacing.pt)},${SINGLE_LINE_RATIO}em)`;
60
60
  }
61
61
  function textDecoration(format) {
62
+ const underline = format.underline !== void 0 && format.underline !== "none" ? format.underline : void 0;
62
63
  const lines = [];
63
- if (format.underline) lines.push("underline");
64
+ if (underline) lines.push("underline");
64
65
  if (format.strike) lines.push("line-through");
65
- if (lines.length === 0) return [];
66
+ if (lines.length === 0) {
67
+ return format.underline === "none" || format.strike === false ? ["text-decoration-line:none"] : [];
68
+ }
66
69
  const css = [`text-decoration-line:${lines.join(" ")}`];
67
- const style = format.underline ? UNDERLINE_STYLES[format.underline] : void 0;
70
+ const style = underline ? UNDERLINE_STYLES[underline] : void 0;
68
71
  if (style) css.push(`text-decoration-style:${style}`);
69
72
  return css;
70
73
  }
71
74
  function inheritedRunCss(format, fontFallbacks) {
72
75
  const css = [];
73
- if (format.bold) css.push("font-weight:bold");
74
- if (format.italic) css.push("font-style:italic");
75
- if (format.smallCaps) css.push("font-variant:small-caps");
76
+ if (format.bold !== void 0) {
77
+ css.push(`font-weight:${format.bold ? "bold" : "normal"}`);
78
+ }
79
+ if (format.italic !== void 0) {
80
+ css.push(`font-style:${format.italic ? "italic" : "normal"}`);
81
+ }
82
+ if (format.smallCaps !== void 0) {
83
+ css.push(`font-variant:${format.smallCaps ? "small-caps" : "normal"}`);
84
+ }
76
85
  css.push(...textDecoration(format));
77
86
  if (format.fontSizePt !== void 0)
78
87
  css.push(`font-size:${pt(format.fontSizePt)}`);
@@ -41,7 +41,14 @@ export declare function setCellBorders(preset: CellBorderPreset): TableCommand;
41
41
  export declare function setCellBorderColor(hex: string | null): TableCommand;
42
42
  /** Fills the selected cells with `#RRGGBB`. Null takes the fill away */
43
43
  export declare function setCellBackground(hex: string | null): TableCommand;
44
- /** Whether direct cell formatting can act on the whole current cell selection. */
44
+ /**
45
+ * Whether direct cell formatting can act on the whole current cell selection.
46
+ *
47
+ * What a cell records about itself is rewritten around its content, which is the block intent the
48
+ * guards answer (`schema/guards`): a cell a control shuts is refused, and so is every cell while a
49
+ * protection shuts the body. The spot asked about stands inside the cell, since that is where the
50
+ * control wrapping it is an ancestor.
51
+ */
45
52
  export declare function canSetCellFormatting(state: EditorState): boolean;
46
53
  /** The fill shared by the selected cells. Null when they differ or none is filled */
47
54
  export declare function activeCellBackground(state: EditorState): string | null;
@@ -8,8 +8,7 @@ import {
8
8
  NO_FILL,
9
9
  toCellFormat
10
10
  } from "../model/format.js";
11
- import { isLockedCell, transactionAllowed } from "../schema/locks.js";
12
- import { editsShut } from "../schema/protectionState.js";
11
+ import { editShut, guardedCommand } from "../schema/guards.js";
13
12
  import { cellDefaultsAt, tableCellSources } from "./gridBorders.js";
14
13
  function text(value) {
15
14
  return typeof value === "string" ? value : null;
@@ -51,19 +50,12 @@ function planChanges(rect, edit) {
51
50
  return changes;
52
51
  }
53
52
  function cellFormatCommand(edit) {
54
- return (state, dispatch) => {
55
- if (!pmIsInTable(state)) return false;
53
+ return guardedCommand((state) => {
54
+ if (!canSetCellFormatting(state)) return null;
56
55
  const rect = selectedRect(state);
57
- if (rect.map.cellsInRect(rect).some((pos) => isLockedCell(rect.table.nodeAt(pos)))) {
58
- return false;
59
- }
60
56
  const changes = planChanges(rect, edit);
61
- if (changes.length === 0) return false;
62
- const transaction = applyChanges(state.tr, rect.tableStart, changes);
63
- if (!transactionAllowed(transaction, state)) return false;
64
- dispatch?.(transaction);
65
- return true;
66
- };
57
+ return changes.length === 0 ? null : applyChanges(state.tr, rect.tableStart, changes);
58
+ });
67
59
  }
68
60
  function shared(values) {
69
61
  const [first, ...rest] = values;
@@ -111,17 +103,18 @@ function selectedCells(state) {
111
103
  const cells = [];
112
104
  for (const pos of rect.map.cellsInRect(rect)) {
113
105
  const cell = rect.table.nodeAt(pos);
114
- if (cell) cells.push(cell);
106
+ if (cell) cells.push({ node: cell, pos: rect.tableStart + pos });
115
107
  }
116
108
  return cells;
117
109
  }
118
110
  function cellFormats(state) {
119
- return selectedCells(state).map((cell) => toCellFormat(cell.attrs.format));
111
+ return selectedCells(state).map(
112
+ ({ node }) => toCellFormat(node.attrs.format)
113
+ );
120
114
  }
121
115
  function canSetCellFormatting(state) {
122
- if (editsShut(state)) return false;
123
116
  const cells = selectedCells(state);
124
- return cells.length > 0 && cells.every((cell) => !isLockedCell(cell));
117
+ return cells.length > 0 && cells.every(({ pos }) => !editShut(state, { kind: "block", at: pos + 1 }));
125
118
  }
126
119
  function sharedValue(values) {
127
120
  const [first, ...rest] = values;
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Building a transaction and running it are kept apart.
3
3
  * Following the ProseMirror convention, calling a command without `dispatch` only
4
- * reports whether it can run right now, and that answer takes in the lock guard
5
- * (`schema/locks`), so no caller has to ask about locks separately.
4
+ * reports whether it can run right now, and that answer takes in every guard an edit
5
+ * is judged by (`schema/guards`), so no caller has to ask about them separately.
6
6
  */
7
7
  import type { EditorState, Transaction } from "prosemirror-state";
8
8
  export type TableCommand = (state: EditorState, dispatch?: (tr: Transaction) => void) => boolean;
@@ -3,7 +3,7 @@ import {
3
3
  deleteTable as pmDeleteTable,
4
4
  isInTable as pmIsInTable
5
5
  } from "prosemirror-tables";
6
- import { transactionAllowed } from "../schema/locks.js";
6
+ import { guardedCommand } from "../schema/guards.js";
7
7
  import {
8
8
  buildAddColumnAfterTransaction,
9
9
  buildAddColumnBeforeTransaction,
@@ -18,26 +18,22 @@ import {
18
18
  function isInTable(state) {
19
19
  return pmIsInTable(state);
20
20
  }
21
- function toCommand(build) {
22
- return (state, dispatch) => {
23
- const tr = build(state);
24
- if (!tr || !transactionAllowed(tr, state)) return false;
25
- dispatch?.(tr);
26
- return true;
27
- };
28
- }
29
- var addRowBefore = toCommand(
21
+ var addRowBefore = guardedCommand(
30
22
  buildAddRowBeforeTransaction
31
23
  );
32
- var addRowAfter = toCommand(buildAddRowAfterTransaction);
33
- var deleteRow = toCommand(buildDeleteRowTransaction);
34
- var addColumnBefore = toCommand(
24
+ var addRowAfter = guardedCommand(
25
+ buildAddRowAfterTransaction
26
+ );
27
+ var deleteRow = guardedCommand(
28
+ buildDeleteRowTransaction
29
+ );
30
+ var addColumnBefore = guardedCommand(
35
31
  buildAddColumnBeforeTransaction
36
32
  );
37
- var addColumnAfter = toCommand(
33
+ var addColumnAfter = guardedCommand(
38
34
  buildAddColumnAfterTransaction
39
35
  );
40
- var deleteColumn = toCommand(
36
+ var deleteColumn = guardedCommand(
41
37
  buildDeleteColumnTransaction
42
38
  );
43
39
  function buildDeleteTableTransaction(state) {
@@ -46,9 +42,15 @@ function buildDeleteTableTransaction(state) {
46
42
  pmDeleteTable(state, (tr) => captured.push(tr));
47
43
  return captured[0] ?? null;
48
44
  }
49
- var deleteTable = toCommand(buildDeleteTableTransaction);
50
- var mergeCells = toCommand(buildMergeCellsTransaction);
51
- var splitCell = toCommand(buildSplitCellTransaction);
45
+ var deleteTable = guardedCommand(
46
+ buildDeleteTableTransaction
47
+ );
48
+ var mergeCells = guardedCommand(
49
+ buildMergeCellsTransaction
50
+ );
51
+ var splitCell = guardedCommand(
52
+ buildSplitCellTransaction
53
+ );
52
54
  export {
53
55
  addColumnAfter,
54
56
  addColumnBefore,
@@ -2,6 +2,9 @@
2
2
  * Newly created cells and rows inherit only the formatting of the cell or row they
3
3
  * are based on. Values that have to be recomputed from the grid (colspan, rowspan,
4
4
  * colwidth) are not inherited.
5
+ *
6
+ * What that comes to per attr is `docx/cloning`, which answers the same question for a
7
+ * paragraph an edit makes out of another paragraph.
5
8
  */
6
9
  import type { Node as PMNode } from "prosemirror-model";
7
10
  import type { GridRect } from "../docx/tableFormatting";
@@ -1,4 +1,5 @@
1
1
  // src/table/format.ts
2
+ import { CLONE_POLICIES } from "../docx/cloning.js";
2
3
  function isNumberArray(value) {
3
4
  return Array.isArray(value) && value.every((entry) => typeof entry === "number");
4
5
  }
@@ -6,18 +7,11 @@ function colwidthOf(cell) {
6
7
  const value = cell.attrs.colwidth;
7
8
  return isNumberArray(value) ? value : null;
8
9
  }
9
- var INHERITED_CELL_ATTRS = ["tcAttrs", "tcPr", "tcW", "format"];
10
10
  function inheritCellAttrs(cell, overrides = {}) {
11
- const attrs = {};
12
- for (const key of INHERITED_CELL_ATTRS) attrs[key] = cell.attrs[key];
13
- return { ...attrs, ...overrides };
11
+ return { ...CLONE_POLICIES.tableCell.attrs(cell, "copy"), ...overrides };
14
12
  }
15
- var INHERITED_ROW_ATTRS = ["trAttrs", "trPr", "format"];
16
13
  function inheritRowAttrs(row) {
17
- if (!row) return null;
18
- const attrs = {};
19
- for (const key of INHERITED_ROW_ATTRS) attrs[key] = row.attrs[key];
20
- return attrs;
14
+ return row ? CLONE_POLICIES.tableRow.attrs(row, "copy") : null;
21
15
  }
22
16
  export {
23
17
  colwidthOf,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The lines a cell draws because of where it sits in the grid, kept true after a structure edit.
2
+ * The lines a cell draws because of where it sits in the grid.
3
3
  *
4
4
  * Every line of a table is drawn by its cells (see `docx/tableFormatting`), so which line a side
5
5
  * falls back on depends on where in the grid the cell sits. A new cell inherits its neighbour's
@@ -9,13 +9,14 @@
9
9
  *
10
10
  * So the cells of a table whose grid moved derive their display values again, along the same path
11
11
  * the import takes. What a cell wrote down itself lives in its `w:tcPr` and is read straight back
12
- * out of it, so only the share that came from the table can change.
12
+ * out of it, so only the share that came from the table can change. `cellFixes` says which cells
13
+ * carry the wrong lines and `sameFormattingInputs` whether anything they depend on moved; the
14
+ * deriver that runs them after an edit is `editor/plugins/tableDisplay`.
13
15
  */
14
16
  import type { Node as PMNode } from "prosemirror-model";
15
- import { Plugin } from "prosemirror-state";
16
17
  import { type CellBorderDefaults } from "../docx/tableFormatting";
17
18
  import { type CellMargins, type InsideBorders, type TableFormat } from "../model/format";
18
- import type { TableGridMap } from "./format";
19
+ import type { NodeAttrs, TableGridMap } from "./format";
19
20
  /**
20
21
  * What a table lays down for its cells to draw.
21
22
  * The values a table style laid down are not in the `tblPr`, so the table carries them separately.
@@ -28,15 +29,12 @@ export interface TableCellSources {
28
29
  export declare function tableCellSources(table: PMNode): TableCellSources;
29
30
  /** The lines the cell at this position falls back on for the sides it draws no border of its own on */
30
31
  export declare function cellDefaultsAt(map: TableGridMap, pos: number, sources: TableCellSources): CellBorderDefaults;
31
- /** Derives shared-border display values before the first editor view is drawn. */
32
- export declare function withDerivedGridBorders(doc: PMNode): PMNode;
33
- /**
34
- * Keeps the derived lines current after a table's grid or border inputs change.
35
- *
36
- * The tables are paired up by the order they stand in. A document that gained or lost a whole table
37
- * pairs the ones after it with the wrong partner, which costs a derivation that changes no cell.
38
- *
39
- * No transaction of its own goes to the history: ProseMirror hands an appended transaction to the
40
- * history as part of the same event, so a single undo takes the edit and this correction back together.
41
- */
42
- export declare function gridBorders(): Plugin;
32
+ export interface CellFix {
33
+ /** The position of the cell within the table's content */
34
+ pos: number;
35
+ attrs: NodeAttrs;
36
+ }
37
+ /** The cells of this table whose display values do not match the spot they now sit in */
38
+ export declare function cellFixes(table: PMNode): CellFix[];
39
+ /** Whether everything the cells' lines are derived from reads the same in both tables */
40
+ export declare function sameFormattingInputs(a: PMNode, b: PMNode): boolean;
@@ -1,7 +1,5 @@
1
1
  // src/table/gridBorders.ts
2
- import { Plugin } from "prosemirror-state";
3
2
  import { TableMap } from "prosemirror-tables";
4
- import { Transform } from "prosemirror-transform";
5
3
  import {
6
4
  cellBorderDefaults,
7
5
  cellMarginsOf,
@@ -129,14 +127,6 @@ function cellFixes(table) {
129
127
  }
130
128
  return fixes;
131
129
  }
132
- function tablesOf(doc) {
133
- const tables = [];
134
- doc.descendants((node, pos) => {
135
- if (node.type.spec.tableRole === "table") tables.push({ pos, table: node });
136
- return !node.isTextblock;
137
- });
138
- return tables;
139
- }
140
130
  function sameSpans(a, b) {
141
131
  return spanCount(a.attrs.colspan) === spanCount(b.attrs.colspan) && spanCount(a.attrs.rowspan) === spanCount(b.attrs.rowspan);
142
132
  }
@@ -156,37 +146,9 @@ function sameCellFormattingInputs(a, b) {
156
146
  function sameFormattingInputs(a, b) {
157
147
  return sameGrid(a, b) && a.attrs.tblPr === b.attrs.tblPr && a.attrs.format === b.attrs.format && a.attrs.styleInside === b.attrs.styleInside && a.attrs.styleCellMargins === b.attrs.styleCellMargins && sameCellFormattingInputs(a, b);
158
148
  }
159
- function withDerivedGridBorders(doc) {
160
- const transform = new Transform(doc);
161
- for (const { pos, table } of tablesOf(doc)) {
162
- for (const fix of cellFixes(table)) {
163
- transform.setNodeMarkup(pos + 1 + fix.pos, null, fix.attrs);
164
- }
165
- }
166
- return transform.doc;
167
- }
168
- function gridBorders() {
169
- return new Plugin({
170
- appendTransaction(transactions, oldState, newState) {
171
- if (!transactions.some((transaction) => transaction.docChanged)) {
172
- return null;
173
- }
174
- const before = tablesOf(oldState.doc);
175
- const tr = newState.tr;
176
- tablesOf(newState.doc).forEach(({ pos, table }, index) => {
177
- const was = before[index]?.table;
178
- if (was && sameFormattingInputs(was, table)) return;
179
- for (const fix of cellFixes(table)) {
180
- tr.setNodeMarkup(pos + 1 + fix.pos, null, fix.attrs);
181
- }
182
- });
183
- return tr.docChanged ? tr : null;
184
- }
185
- });
186
- }
187
149
  export {
188
150
  cellDefaultsAt,
189
- gridBorders,
190
- tableCellSources,
191
- withDerivedGridBorders
151
+ cellFixes,
152
+ sameFormattingInputs,
153
+ tableCellSources
192
154
  };
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * Only what the toolbar needs is exported. Following the ProseMirror convention,
5
5
  * calling a command without `dispatch` only reports whether it can run right now, and that answer
6
- * takes in the lock: a structural edit that would carry a locked cell off reports that it does not
7
- * apply, rather than being refused after the click (`schema/locks`).
6
+ * takes in every guard an edit is judged by (`schema/guards`): a structural edit that would carry a
7
+ * locked cell off reports that it does not apply, rather than being refused after the click.
8
8
  *
9
9
  * Everything here works on a table already in the document; putting a new one into the body is
10
10
  * `insertTable` in `./commands`, which reads the page geometry the editor holds.
@@ -6,18 +6,14 @@ import type { EditorState, Transaction } from "prosemirror-state";
6
6
  /**
7
7
  * Whether several cells can be merged into one.
8
8
  * The selection has to be a rectangle spanning more than one cell, no merged cell may stick out
9
- * past that rectangle, and the lock guard has to let the merge through: a locked cell may not be
10
- * swallowed by another (`schema/locks`).
11
- *
12
- * The two queries are defined from the very transaction the commands run, so they cannot drift
13
- * from them. They live here rather than beside the commands because `./commands` imports these
14
- * builders, and the other way round would turn that import around.
9
+ * past that rectangle, and the guards have to let the merge through: a locked cell may not be
10
+ * swallowed by another (`schema/guards`).
15
11
  */
16
12
  export declare function canMergeCells(state: EditorState): boolean;
17
13
  /**
18
14
  * Whether a split is possible. The cursor has to sit inside a cell that is merged horizontally or
19
- * vertically, and the lock guard has to let the split through: the cells a split makes would each
20
- * carry the original's lock, which is a lock planted in places it was never put (`schema/locks`).
15
+ * vertically, and the guards have to let the split through: the cells a split makes would each
16
+ * carry the original's lock, which is a lock planted in places it was never put (`schema/guards`).
21
17
  */
22
18
  export declare function canSplitCell(state: EditorState): boolean;
23
19
  /**
@@ -11,7 +11,7 @@ import {
11
11
  widthNumber,
12
12
  withWidthNumber
13
13
  } from "../model/format.js";
14
- import { transactionAllowed } from "../schema/locks.js";
14
+ import { guardedCommand } from "../schema/guards.js";
15
15
  import { inheritCellAttrs } from "./format.js";
16
16
  import { cellWidthForGridCol, gridSpanWidth, tableGridCols } from "./widths.js";
17
17
  function tableAfter(tr, tableStart) {
@@ -24,13 +24,13 @@ function cellAt(rect, row, col) {
24
24
  const pos = rect.map.map[row * rect.map.width + col];
25
25
  return pos == null ? null : rect.table.nodeAt(pos);
26
26
  }
27
+ var merging = guardedCommand(buildMergeCellsTransaction);
28
+ var splitting = guardedCommand(buildSplitCellTransaction);
27
29
  function canMergeCells(state) {
28
- const tr = buildMergeCellsTransaction(state);
29
- return tr !== null && transactionAllowed(tr, state);
30
+ return merging(state);
30
31
  }
31
32
  function canSplitCell(state) {
32
- const tr = buildSplitCellTransaction(state);
33
- return tr !== null && transactionAllowed(tr, state);
33
+ return splitting(state);
34
34
  }
35
35
  function coveredWidthSum(rect, type) {
36
36
  let sum = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portone/docx-editor",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "A simple DOCX editor for React, built directly on OOXML.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -76,7 +76,9 @@
76
76
  "@biomejs/biome": "2.5.10",
77
77
  "@changesets/changelog-github": "^1.0.0",
78
78
  "@changesets/cli": "^3.0.1",
79
+ "@microsoft/api-extractor": "7.59.0",
79
80
  "@playwright/test": "^1.62.1",
81
+ "@types/jsdom": "^30.0.0",
80
82
  "@types/node": "^26.2.0",
81
83
  "@types/react": "19.2.18",
82
84
  "@types/react-dom": "19.2.5",
@@ -105,16 +107,21 @@
105
107
  "build:demo": "vite build --config demo/vite.config.ts",
106
108
  "dev:site": "pnpm --filter @portone/docx-editor-site dev",
107
109
  "build:site": "pnpm --filter @portone/docx-editor-site build",
108
- "check": "pnpm lint && pnpm typecheck && pnpm test",
110
+ "check": "pnpm lint && pnpm typecheck && pnpm test && pnpm test:fixtures && pnpm test:site-release && pnpm check:demo-library",
109
111
  "typecheck": "tsc --noEmit && tsc --noEmit -p e2e && tsc --noEmit -p demo && pnpm --filter @portone/docx-editor-site typecheck",
110
112
  "lint": "pnpm biome check . --diagnostic-level=error",
111
113
  "test": "vitest run",
114
+ "test:fixtures": "node --test scripts/sanitize-fixture.test.mjs",
112
115
  "test:package": "vitest run --dir packaging --no-file-parallelism",
113
116
  "verify:package": "node scripts/verify-package.mjs",
117
+ "check:demo-library": "node scripts/demo-library-pin.mjs",
118
+ "pin:demo-library": "node scripts/demo-library-pin.mjs --write",
119
+ "api:update": "pnpm build && node scripts/api-report.mjs --local",
114
120
  "spec": "node scripts/index-ooxml-spec.mjs",
115
121
  "changeset": "changeset",
116
122
  "changeset:version": "changeset version",
117
- "test:e2e": "playwright test"
123
+ "test:e2e": "playwright test",
124
+ "test:site-release": "node --test scripts/site-release.test.mjs"
118
125
  },
119
126
  "types": "./dist/index.d.ts"
120
127
  }
@@ -1,7 +0,0 @@
1
- /** Declares a part an export adds in `[Content_Types].xml`, leaving the original text as it came. */
2
- /**
3
- * The content types part with an override for this part, or null when it already declares one.
4
- * `current` is the part as an earlier addition in the same export left it, so that two additions
5
- * do not each write over the other's declaration.
6
- */
7
- export declare function withContentType(parts: Map<string, Uint8Array>, partPath: string, contentType: string, current: Uint8Array | undefined): Uint8Array | null;
@@ -1,38 +0,0 @@
1
- // src/docx/comments/contentTypes.ts
2
- import { DocxExportError } from "../../ooxml/errors.js";
3
- import { decodeUtf8, encodeUtf8 } from "../../ooxml/xml.js";
4
- import { CONTENT_TYPES_PATH } from "./constants.js";
5
- var TYPES_OPEN_TAG = /<(?:[\w.-]+:)?Types\b[^>]*>/;
6
- function withContentType(parts, partPath, contentType, current) {
7
- const original = current ?? parts.get(CONTENT_TYPES_PATH);
8
- if (!original) {
9
- throw new DocxExportError(
10
- "missing-content-types",
11
- `cannot add a part to a package that has no ${CONTENT_TYPES_PATH}`
12
- );
13
- }
14
- const { text, hadBom } = decodeUtf8(original);
15
- const partName = `/${partPath}`;
16
- if (new RegExp(
17
- `<(?:[\\w.-]+:)?Override[^>]+PartName=["']${partName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}["']`,
18
- "i"
19
- ).test(text)) {
20
- return null;
21
- }
22
- const open = TYPES_OPEN_TAG.exec(text);
23
- if (!open) {
24
- throw new DocxExportError(
25
- "malformed-xml",
26
- `${CONTENT_TYPES_PATH} has no Types element`
27
- );
28
- }
29
- const rootName = /^<([^\s>]+)/.exec(open[0])?.[1] ?? "Types";
30
- const separator = rootName.indexOf(":");
31
- const prefix = separator < 0 ? "" : `${rootName.slice(0, separator)}:`;
32
- const declaration = `<${prefix}Override PartName="${partName}" ContentType="${contentType}"/>`;
33
- const at = open.index + open[0].length;
34
- return encodeUtf8(text.slice(0, at) + declaration + text.slice(at), hadBom);
35
- }
36
- export {
37
- withContentType
38
- };
@@ -1,15 +0,0 @@
1
- import { type ParagraphFormat } from "../../model/format";
2
- import { type Numbering } from "../../numbering/parseNumbering";
3
- import { type StyleFormat, type StyleTable } from "./styles";
4
- import { type ParagraphFormatLayer } from "./tabStops";
5
- export interface ParagraphFormattingContext {
6
- styles: StyleTable;
7
- defaultStyleId: string | null;
8
- defaults: ParagraphFormatLayer;
9
- numbering: Numbering;
10
- }
11
- export declare const NO_PARAGRAPH_FORMATTING: ParagraphFormattingContext;
12
- /** Resolves the effective OOXML paragraph-property hierarchy for display. */
13
- export declare function effectiveParagraphFormat(pPr: unknown, context: ParagraphFormattingContext): ParagraphFormat | null;
14
- /** Resolves the run properties supplied by the paragraph style. */
15
- export declare function effectiveParagraphStyle(pPr: unknown, context: ParagraphFormattingContext): StyleFormat | undefined;