@portone/docx-editor 0.2.1 → 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 (225) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/CONTRIBUTING.md +5 -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 +12 -7
  10. package/dist/docx/commentOnlyChange.js +9 -156
  11. package/dist/docx/comments/constants.d.ts +0 -1
  12. package/dist/docx/comments/constants.js +0 -2
  13. package/dist/docx/comments/grammar.d.ts +10 -4
  14. package/dist/docx/comments/grammar.js +12 -2
  15. package/dist/docx/comments/{verifying.d.ts → parts.d.ts} +26 -18
  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 +16 -92
  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.js +8 -10
  22. package/dist/docx/comments/writing.d.ts +19 -7
  23. package/dist/docx/comments/writing.js +52 -111
  24. package/dist/docx/documentSettings.d.ts +7 -0
  25. package/dist/docx/documentSettings.js +10 -2
  26. package/dist/docx/exportDocx.d.ts +40 -5
  27. package/dist/docx/exportDocx.js +69 -71
  28. package/dist/docx/exportRefs.d.ts +5 -2
  29. package/dist/docx/exportRefs.js +3 -1
  30. package/dist/docx/fidelity.d.ts +43 -0
  31. package/dist/docx/fidelity.js +64 -0
  32. package/dist/docx/formatting/attrs.d.ts +27 -0
  33. package/dist/docx/formatting/attrs.js +31 -0
  34. package/dist/docx/formatting/context.d.ts +31 -0
  35. package/dist/docx/formatting/context.js +42 -0
  36. package/dist/docx/formatting/direct.d.ts +6 -5
  37. package/dist/docx/formatting/direct.js +52 -78
  38. package/dist/docx/formatting/resolve.d.ts +45 -0
  39. package/dist/docx/formatting/resolve.js +140 -0
  40. package/dist/docx/formatting/runProperties.d.ts +93 -0
  41. package/dist/docx/formatting/runProperties.js +316 -0
  42. package/dist/docx/formatting/styles.js +3 -3
  43. package/dist/docx/formatting/tabStops.js +7 -22
  44. package/dist/docx/formatting.d.ts +4 -1
  45. package/dist/docx/formatting.js +4 -1
  46. package/dist/docx/headersFooters.js +6 -13
  47. package/dist/docx/identities.d.ts +67 -0
  48. package/dist/docx/identities.js +174 -0
  49. package/dist/docx/importDocx.d.ts +12 -1
  50. package/dist/docx/importDocx.js +72 -79
  51. package/dist/docx/importParagraph.d.ts +1 -1
  52. package/dist/docx/importParagraph.js +4 -3
  53. package/dist/docx/importTable.d.ts +1 -1
  54. package/dist/docx/importTable.js +17 -1
  55. package/dist/docx/invariants.d.ts +33 -0
  56. package/dist/docx/invariants.js +256 -0
  57. package/dist/docx/media.d.ts +6 -4
  58. package/dist/docx/media.js +6 -37
  59. package/dist/docx/newLists.d.ts +20 -0
  60. package/dist/docx/newLists.js +36 -0
  61. package/dist/docx/notes.js +3 -7
  62. package/dist/docx/numberingPlanner.d.ts +8 -0
  63. package/dist/docx/numberingPlanner.js +19 -0
  64. package/dist/docx/packageParts.d.ts +42 -0
  65. package/dist/docx/packageParts.js +135 -0
  66. package/dist/docx/pageGeometry.d.ts +2 -0
  67. package/dist/docx/pageGeometry.js +18 -9
  68. package/dist/docx/paraProps.d.ts +10 -22
  69. package/dist/docx/paraProps.js +47 -76
  70. package/dist/docx/partPlan.d.ts +36 -0
  71. package/dist/docx/partPlan.js +59 -0
  72. package/dist/docx/protectionPolicy.d.ts +127 -0
  73. package/dist/docx/protectionPolicy.js +169 -0
  74. package/dist/docx/relationships.d.ts +1 -1
  75. package/dist/docx/relationships.js +8 -13
  76. package/dist/docx/runProps.d.ts +9 -22
  77. package/dist/docx/runProps.js +15 -168
  78. package/dist/docx/scan.js +20 -52
  79. package/dist/docx/sdt.js +9 -34
  80. package/dist/docx/sdtProps.d.ts +8 -1
  81. package/dist/docx/sdtProps.js +10 -0
  82. package/dist/docx/serializeBlock.d.ts +2 -0
  83. package/dist/docx/serializeBlock.js +8 -5
  84. package/dist/docx/serializeTable.js +32 -15
  85. package/dist/docx/session.d.ts +42 -14
  86. package/dist/docx/session.js +43 -17
  87. package/dist/docx/tableFormatting/editing.js +108 -139
  88. package/dist/docx/tableFormatting/reading.d.ts +16 -6
  89. package/dist/docx/tableFormatting/reading.js +40 -20
  90. package/dist/docx/tableTemplate.js +18 -7
  91. package/dist/download.d.ts +8 -5
  92. package/dist/download.js +2 -0
  93. package/dist/editor/clipboard/inlineFormatting.js +19 -30
  94. package/dist/editor/commands/comments/editing.d.ts +6 -2
  95. package/dist/editor/commands/comments/editing.js +19 -31
  96. package/dist/editor/commands/exportQueries.d.ts +15 -0
  97. package/dist/editor/commands/exportQueries.js +14 -0
  98. package/dist/editor/commands/fidelityQueries.d.ts +12 -0
  99. package/dist/editor/commands/fidelityQueries.js +8 -0
  100. package/dist/editor/commands/formatting/editing.d.ts +2 -2
  101. package/dist/editor/commands/formatting/editing.js +26 -108
  102. package/dist/editor/commands/formatting/propertyCommands.d.ts +21 -0
  103. package/dist/editor/commands/formatting/propertyCommands.js +103 -0
  104. package/dist/editor/commands/formatting/shared.d.ts +3 -3
  105. package/dist/editor/commands/formatting/shared.js +5 -2
  106. package/dist/editor/commands/indentCommands.js +5 -4
  107. package/dist/editor/commands/index.d.ts +11 -0
  108. package/dist/editor/commands/index.js +5 -0
  109. package/dist/editor/commands/linkCommands.js +5 -6
  110. package/dist/editor/commands/listCommands.js +8 -10
  111. package/dist/editor/commands/lockCommands.d.ts +7 -1
  112. package/dist/editor/commands/lockCommands.js +35 -46
  113. package/dist/editor/commands/paragraphCommands.js +28 -31
  114. package/dist/editor/commands/spacingCommands.js +1 -1
  115. package/dist/editor/createEditor.d.ts +17 -29
  116. package/dist/editor/createEditor.js +58 -58
  117. package/dist/editor/documentStyles.d.ts +11 -35
  118. package/dist/editor/documentStyles.js +9 -52
  119. package/dist/editor/editorDocument.d.ts +56 -0
  120. package/dist/editor/editorDocument.js +74 -0
  121. package/dist/editor/externalClipboard.js +16 -45
  122. package/dist/editor/insertTable.js +4 -3
  123. package/dist/editor/paragraphEdits.d.ts +11 -18
  124. package/dist/editor/paragraphEdits.js +7 -16
  125. package/dist/editor/plugins/displayDerivation.d.ts +47 -0
  126. package/dist/editor/plugins/displayDerivation.js +78 -0
  127. package/dist/editor/plugins/imagePaste.js +4 -3
  128. package/dist/editor/plugins/keymap.js +18 -3
  129. package/dist/editor/plugins/numberingDecorations.d.ts +5 -11
  130. package/dist/editor/plugins/numberingDecorations.js +7 -17
  131. package/dist/editor/plugins/paragraphDisplay.d.ts +7 -0
  132. package/dist/editor/plugins/paragraphDisplay.js +51 -0
  133. package/dist/editor/plugins/tabLayout.js +1 -1
  134. package/dist/editor/plugins/tableDisplay.d.ts +6 -0
  135. package/dist/editor/plugins/tableDisplay.js +16 -0
  136. package/dist/index.d.ts +2 -0
  137. package/dist/model/format.d.ts +30 -8
  138. package/dist/model/format.js +35 -22
  139. package/dist/model/tabStops.d.ts +9 -0
  140. package/dist/model/tabStops.js +18 -0
  141. package/dist/numbering/listTemplate.js +24 -7
  142. package/dist/numbering/parseNumbering.d.ts +14 -1
  143. package/dist/numbering/parseNumbering.js +35 -22
  144. package/dist/numbering/writeNumbering.d.ts +3 -4
  145. package/dist/numbering/writeNumbering.js +14 -25
  146. package/dist/ooxml/childOrder.d.ts +34 -0
  147. package/dist/ooxml/childOrder.js +496 -0
  148. package/dist/ooxml/element.d.ts +24 -11
  149. package/dist/ooxml/element.js +31 -12
  150. package/dist/ooxml/errors.d.ts +3 -2
  151. package/dist/ooxml/image.d.ts +4 -0
  152. package/dist/ooxml/image.js +2 -5
  153. package/dist/ooxml/partSplice.d.ts +67 -0
  154. package/dist/ooxml/partSplice.js +169 -0
  155. package/dist/ooxml/props.d.ts +112 -0
  156. package/dist/{docx/propsXml.js → ooxml/props.js} +72 -200
  157. package/dist/ooxml/simpleTypes.d.ts +103 -0
  158. package/dist/ooxml/simpleTypes.js +182 -0
  159. package/dist/ooxml/tabStops.js +8 -27
  160. package/dist/ooxml/tagScan.d.ts +34 -0
  161. package/dist/ooxml/tagScan.js +108 -0
  162. package/dist/ooxml/units.d.ts +25 -13
  163. package/dist/ooxml/units.js +65 -31
  164. package/dist/ooxml/xml.d.ts +27 -6
  165. package/dist/ooxml/xml.js +37 -5
  166. package/dist/page/blockKinds.d.ts +61 -0
  167. package/dist/page/blockKinds.js +11 -0
  168. package/dist/page/kinds/index.d.ts +6 -0
  169. package/dist/page/kinds/index.js +10 -0
  170. package/dist/page/kinds/paragraphKind.d.ts +10 -0
  171. package/dist/page/kinds/paragraphKind.js +71 -0
  172. package/dist/page/kinds/tableKind.d.ts +10 -0
  173. package/dist/page/kinds/tableKind.js +178 -0
  174. package/dist/page/measureBlocks.d.ts +3 -14
  175. package/dist/page/measureBlocks.js +24 -45
  176. package/dist/page/pageDecorations.d.ts +27 -44
  177. package/dist/page/pageDecorations.js +70 -152
  178. package/dist/page/pageLayout.d.ts +2 -19
  179. package/dist/page/pageLayout.js +36 -6
  180. package/dist/page/usePageLayout.d.ts +2 -17
  181. package/dist/page/usePageLayout.js +3 -39
  182. package/dist/schema/attrRoles.d.ts +25 -18
  183. package/dist/schema/attrRoles.js +102 -67
  184. package/dist/schema/displayDerivation.d.ts +82 -0
  185. package/dist/schema/displayDerivation.js +130 -0
  186. package/dist/schema/docxSchema.d.ts +3 -0
  187. package/dist/schema/docxSchema.js +26 -14
  188. package/dist/schema/editGuard.d.ts +1 -1
  189. package/dist/schema/guards.d.ts +37 -14
  190. package/dist/schema/guards.js +29 -3
  191. package/dist/schema/index.d.ts +2 -0
  192. package/dist/schema/index.js +2 -0
  193. package/dist/schema/locks.d.ts +11 -44
  194. package/dist/schema/locks.js +0 -9
  195. package/dist/schema/preservedGuards.d.ts +26 -6
  196. package/dist/schema/preservedGuards.js +31 -1
  197. package/dist/schema/protection.d.ts +7 -1
  198. package/dist/schema/protection.js +2 -1
  199. package/dist/schema/sourceEquality.d.ts +1 -9
  200. package/dist/schema/sourceEquality.js +1 -27
  201. package/dist/styles/inlineStyle.js +15 -6
  202. package/dist/table/cellFormatting.d.ts +8 -1
  203. package/dist/table/cellFormatting.js +10 -18
  204. package/dist/table/commands.js +20 -18
  205. package/dist/table/format.d.ts +3 -0
  206. package/dist/table/format.js +3 -9
  207. package/dist/table/gridBorders.d.ts +14 -16
  208. package/dist/table/gridBorders.js +3 -41
  209. package/dist/table/merge.d.ts +2 -6
  210. package/dist/table/merge.js +5 -5
  211. package/package.json +7 -3
  212. package/dist/docx/comments/contentTypes.d.ts +0 -7
  213. package/dist/docx/comments/contentTypes.js +0 -38
  214. package/dist/docx/comments/verifying.js +0 -206
  215. package/dist/docx/formatting/effectiveParagraph.d.ts +0 -15
  216. package/dist/docx/formatting/effectiveParagraph.js +0 -81
  217. package/dist/docx/propsXml.d.ts +0 -67
  218. package/dist/docx/uniqueControls.d.ts +0 -14
  219. package/dist/docx/uniqueControls.js +0 -62
  220. package/dist/editor/plugins/commentReservations.d.ts +0 -5
  221. package/dist/editor/plugins/commentReservations.js +0 -26
  222. package/dist/editor/plugins/styledParagraphs.d.ts +0 -15
  223. package/dist/editor/plugins/styledParagraphs.js +0 -65
  224. package/dist/page/tableMeasurements.d.ts +0 -18
  225. package/dist/page/tableMeasurements.js +0 -119
@@ -1,18 +1,23 @@
1
1
  // src/docx/tableFormatting/editing.ts
2
- import { attrPairs, elementXml } from "../../ooxml/element.js";
2
+ import { elementXml, wAttrValue } from "../../ooxml/element.js";
3
3
  import { wName } from "../../ooxml/names.js";
4
4
  import { setAttr } from "../../ooxml/precedence.js";
5
- import { normalizeHex, wAttr } from "../../ooxml/units.js";
6
- import { childByLocalName } from "../../ooxml/xml.js";
7
5
  import {
6
+ childElement,
7
+ editChild,
8
8
  parseProps,
9
9
  parsePropsXml,
10
10
  propsChild,
11
11
  renderProps,
12
- setPropsChild,
13
- TC_PR_ORDER,
14
- TR_PR_ORDER
15
- } from "../propsXml.js";
12
+ setChild,
13
+ withAttrs
14
+ } from "../../ooxml/props.js";
15
+ import {
16
+ ST_EighthPointMeasure,
17
+ ST_TwipsMeasure,
18
+ TWIPS_PER_PT
19
+ } from "../../ooxml/simpleTypes.js";
20
+ import { normalizeHex } from "../../ooxml/units.js";
16
21
  import {
17
22
  NO_BORDER_DEFAULTS,
18
23
  NO_CELL_MARGINS,
@@ -25,18 +30,6 @@ var ALL_CELL_SIDES = [
25
30
  "left",
26
31
  "right"
27
32
  ];
28
- var TC_BORDERS_ORDER = [
29
- "top",
30
- "start",
31
- "left",
32
- "bottom",
33
- "end",
34
- "right",
35
- "insideH",
36
- "insideV",
37
- "tl2br",
38
- "tr2bl"
39
- ];
40
33
  var SIDE_NAMES = {
41
34
  top: ["top"],
42
35
  bottom: ["bottom"],
@@ -46,22 +39,24 @@ var SIDE_NAMES = {
46
39
  var PRESET_BORDER_EIGHTHS = 4;
47
40
  var EMPTY_TC_PR = { tag: "w:tcPr", attrs: null, children: [] };
48
41
  function drawsLine(side) {
49
- if (!side) return false;
50
- const val = wAttr(side, "val");
42
+ if (side.tag === null) return false;
43
+ const val = wAttrValue(side.attrs, "val");
51
44
  return val !== null && val !== "nil" && val !== "none";
52
45
  }
53
46
  function sideAttrs(name, current, edit, fallback = null) {
54
47
  if (edit.kind === "color") {
55
48
  if (!drawsLine(current)) {
56
- if (current || !fallback || fallback === "none") return null;
49
+ if (current.tag !== null || fallback === null || fallback.val === "none") {
50
+ return null;
51
+ }
57
52
  return inheritedBorderAttrs(fallback, edit.hex);
58
53
  }
59
- return setAttr(attrPairs(current), name, "color", edit.hex ?? "auto");
54
+ return setAttr(current.attrs, name, "color", edit.hex ?? "auto");
60
55
  }
61
56
  if (edit.line === "none") {
62
- return current ? setAttr(attrPairs(current), name, "val", "none") : [[wName("val"), "none"]];
57
+ return current.tag !== null ? setAttr(current.attrs, name, "val", "none") : [[wName("val"), "none"]];
63
58
  }
64
- if (!current) {
59
+ if (current.tag === null) {
65
60
  return [
66
61
  [wName("val"), "single"],
67
62
  [wName("sz"), `${PRESET_BORDER_EIGHTHS}`],
@@ -70,217 +65,191 @@ function sideAttrs(name, current, edit, fallback = null) {
70
65
  ];
71
66
  }
72
67
  return setAttr(
73
- setAttr(attrPairs(current), name, "val", "single"),
68
+ setAttr(current.attrs, name, "val", "single"),
74
69
  name,
75
70
  "sz",
76
71
  `${PRESET_BORDER_EIGHTHS}`
77
72
  );
78
73
  }
79
- var BORDER_VAL_BY_CSS_STYLE = {
80
- solid: "single",
81
- double: "double",
82
- dashed: "dashed",
83
- dotted: "dotted"
84
- };
85
- function inheritedBorderAttrs(border, hex) {
86
- const matched = border.match(
87
- /^(\d+(?:\.\d+)?)pt (solid|double|dashed|dotted) (#[0-9a-f]{6})$/i
88
- );
89
- if (!matched) return null;
90
- const [, widthText, style, currentColor] = matched;
91
- if (hex !== null && normalizeHex(currentColor) === hex) return null;
92
- const eighths = Math.round(Number.parseFloat(widthText) * 8);
93
- if (!Number.isSafeInteger(eighths) || eighths <= 0) return null;
74
+ function inheritedBorderAttrs(line, hex) {
75
+ const current = line.color.kind === "rgb" ? normalizeHex(line.color.hex) : null;
76
+ if (hex !== null && current === hex) return null;
77
+ const thickness = ST_EighthPointMeasure.format(line.eighths);
78
+ if (thickness === null) return null;
94
79
  return [
95
- [wName("val"), BORDER_VAL_BY_CSS_STYLE[style.toLowerCase()]],
96
- [wName("sz"), `${eighths}`],
80
+ [wName("val"), line.val],
81
+ [wName("sz"), thickness],
97
82
  [wName("space"), "0"],
98
83
  [wName("color"), hex ?? "auto"]
99
84
  ];
100
85
  }
101
86
  function sideNames(borders, side) {
102
87
  const [primary, alternate] = SIDE_NAMES[side];
103
- const usesAlternate = alternate !== void 0 && borders !== null && childByLocalName(borders, primary) === null && childByLocalName(borders, alternate) !== null;
88
+ const usesAlternate = alternate !== void 0 && propsChild(borders.children, primary) === void 0 && propsChild(borders.children, alternate) !== void 0;
104
89
  if (usesAlternate) return { write: alternate, drop: null };
105
90
  return { write: primary, drop: alternate ?? null };
106
91
  }
107
- function editedBorders(current, edit, defaults = NO_BORDER_DEFAULTS) {
108
- const props = current === null ? { tag: "w:tcBorders", attrs: null, children: [] } : parseProps(current);
109
- const borders = current === null ? null : parsePropsXml(current);
110
- if (!props || current !== null && !borders) return null;
92
+ function leafElement(tag, attrs) {
93
+ return withAttrs({ tag, attrs: null, children: [] }, attrs);
94
+ }
95
+ function containerOf(props, name) {
96
+ const current = propsChild(props.children, name)?.xml;
97
+ if (current === void 0) {
98
+ return { tag: wName(name), attrs: null, children: [] };
99
+ }
100
+ return parseProps(current);
101
+ }
102
+ function editedBorders(props, edit, defaults) {
103
+ const borders = containerOf(props, "tcBorders");
104
+ if (!borders) return null;
111
105
  const sides = edit.kind === "line" ? edit.sides : ALL_CELL_SIDES;
112
- const edited = sides.reduce((kept, side) => {
106
+ return sides.reduce((kept, side) => {
107
+ if (kept === null) return null;
113
108
  const { write, drop } = sideNames(borders, side);
114
- const currentSide = borders ? childByLocalName(borders, write) : null;
109
+ const currentSide = childElement(borders, write);
110
+ if (!currentSide) return null;
115
111
  const attrs = sideAttrs(write, currentSide, edit, defaults[side]);
116
112
  if (!attrs) return kept;
117
- const tag = currentSide?.nodeName ?? wName(write);
118
- const written = setPropsChild(
113
+ const written = editChild(
119
114
  kept,
120
- write,
121
- elementXml(tag, attrs),
122
- TC_BORDERS_ORDER
115
+ ["tcBorders", write],
116
+ () => leafElement(currentSide.tag ?? wName(write), attrs)
123
117
  );
124
- return drop === null ? written : setPropsChild(written, drop, null, TC_BORDERS_ORDER);
118
+ if (written === null || drop === null) return written;
119
+ return editChild(written, ["tcBorders", drop], () => null);
125
120
  }, props);
126
- const xml = renderProps(edited);
127
- return { xml: xml === "" ? null : xml };
128
121
  }
129
122
  function hasPattern(shd) {
130
- const val = shd ? wAttr(shd, "val") : null;
123
+ const val = wAttrValue(shd.attrs, "val");
131
124
  return val !== null && val !== "clear" && val !== "nil";
132
125
  }
133
126
  function editedShading(current, fill) {
134
- const tag = current?.nodeName ?? wName("shd");
127
+ const tag = current.tag ?? wName("shd");
135
128
  if (fill === null) {
136
129
  if (!hasPattern(current)) return null;
137
- return elementXml(tag, setAttr(attrPairs(current), "shd", "fill", "auto"));
130
+ return elementXml(tag, setAttr(current.attrs, "shd", "fill", "auto"));
138
131
  }
139
- if (!current) {
132
+ if (current.tag === null) {
140
133
  return elementXml(tag, [
141
134
  [wName("val"), "clear"],
142
135
  [wName("color"), "auto"],
143
136
  [wName("fill"), fill]
144
137
  ]);
145
138
  }
146
- const painting = hasPattern(current) ? attrPairs(current) : setAttr(attrPairs(current), "shd", "val", "clear");
139
+ const painting = hasPattern(current) ? current.attrs : setAttr(current.attrs, "shd", "val", "clear");
147
140
  return elementXml(tag, setAttr(painting, "shd", "fill", fill));
148
141
  }
149
- function bordersChange(props, edit, defaults) {
150
- const current = propsChild(props.children, "tcBorders")?.xml ?? null;
151
- const edited = editedBorders(current, edit, defaults);
152
- return edited === null ? null : { name: "tcBorders", xml: edited.xml };
153
- }
154
- var TC_MAR_ORDER = [
155
- "top",
156
- "start",
157
- "left",
158
- "bottom",
159
- "end",
160
- "right"
161
- ];
162
142
  var MARGIN_SIDE_NAMES = {
163
143
  top: ["top"],
164
144
  right: ["end", "right"],
165
145
  bottom: ["bottom"],
166
146
  left: ["start", "left"]
167
147
  };
168
- function paddingChange(props, values) {
169
- const current = propsChild(props.children, "tcMar")?.xml ?? null;
170
- const margins = current === null ? null : parsePropsXml(current);
171
- const parsed = current === null ? { tag: "w:tcMar", attrs: null, children: [] } : parseProps(current);
172
- if (!parsed || current !== null && !margins) return null;
173
- let edited = parsed;
148
+ function editedPadding(props, values) {
149
+ const margins = containerOf(props, "tcMar");
150
+ if (!margins) return null;
151
+ let edited = props;
174
152
  let wrote = false;
175
153
  for (const side of ALL_CELL_SIDES) {
176
154
  const points = values[side];
177
155
  if (points === void 0) continue;
178
- const twips = Math.round(points * 20);
179
- if (!Number.isFinite(points) || points < 0 || !Number.isSafeInteger(twips)) {
180
- return null;
181
- }
182
- const existing = margins ? MARGIN_SIDE_NAMES[side].map((name2) => childByLocalName(margins, name2)).find((element) => element !== null) ?? null : null;
183
- const name = existing?.localName ?? side;
156
+ if (points < 0) return null;
157
+ const twips = ST_TwipsMeasure.format(Math.round(points * TWIPS_PER_PT));
158
+ if (twips === null) return null;
159
+ const name = MARGIN_SIDE_NAMES[side].find(
160
+ (spelling) => propsChild(margins.children, spelling) !== void 0
161
+ ) ?? side;
162
+ const existing = childElement(margins, name);
163
+ if (existing === null || edited === null) return null;
184
164
  const attrs = setAttr(
185
- setAttr(attrPairs(existing), name, "w", `${twips}`),
165
+ setAttr(existing.attrs, name, "w", twips),
186
166
  name,
187
167
  "type",
188
168
  "dxa"
189
169
  );
190
- edited = setPropsChild(
170
+ edited = editChild(
191
171
  edited,
192
- name,
193
- elementXml(existing?.nodeName ?? wName(side), attrs),
194
- TC_MAR_ORDER
172
+ ["tcMar", name],
173
+ () => leafElement(existing.tag ?? wName(side), attrs)
195
174
  );
196
175
  wrote = true;
197
176
  }
198
- if (!wrote) return null;
199
- return { name: "tcMar", xml: renderProps(edited) };
177
+ return wrote ? edited : null;
200
178
  }
201
- function childChange(edit, props, defaults) {
179
+ function editedProps(edit, props, defaults) {
202
180
  switch (edit.kind) {
203
181
  case "background": {
204
182
  const fill = edit.hex === null ? null : normalizeHex(edit.hex);
205
183
  if (edit.hex !== null && fill === null) return null;
206
- const current = propsChild(props.children, "shd")?.xml ?? null;
207
- const shd = current === null ? null : parsePropsXml(current);
208
- if (current !== null && !shd) return null;
209
- return { name: "shd", xml: editedShading(shd, fill) };
184
+ const shd = childElement(props, "shd");
185
+ if (!shd) return null;
186
+ return setChild(props, "shd", editedShading(shd, fill));
210
187
  }
211
188
  case "borders":
212
- return bordersChange(
189
+ return editedBorders(
213
190
  props,
214
- {
215
- kind: "line",
216
- line: edit.line,
217
- sides: edit.sides
218
- },
191
+ { kind: "line", line: edit.line, sides: edit.sides },
219
192
  defaults
220
193
  );
221
194
  case "borderColor": {
222
195
  const hex = edit.hex === null ? null : normalizeHex(edit.hex);
223
196
  if (edit.hex !== null && hex === null) return null;
224
- return bordersChange(props, { kind: "color", hex }, defaults);
197
+ return editedBorders(props, { kind: "color", hex }, defaults);
225
198
  }
226
199
  case "verticalAlign": {
227
- const current = propsChild(props.children, "vAlign")?.xml ?? null;
228
- const element = current === null ? null : parsePropsXml(current);
229
- if (current !== null && !element) return null;
230
- return {
231
- name: "vAlign",
232
- xml: elementXml(
233
- element?.nodeName ?? wName("vAlign"),
234
- setAttr(attrPairs(element), "vAlign", "val", edit.align)
200
+ const current = childElement(props, "vAlign");
201
+ if (!current) return null;
202
+ return setChild(
203
+ props,
204
+ "vAlign",
205
+ elementXml(
206
+ current.tag ?? wName("vAlign"),
207
+ setAttr(current.attrs, "vAlign", "val", edit.align)
235
208
  )
236
- };
209
+ );
237
210
  }
238
211
  case "padding":
239
- return paddingChange(props, edit.values);
212
+ return editedPadding(props, edit.values);
240
213
  }
241
214
  }
242
215
  function editCellProps(tcPr, edit, defaults = NO_BORDER_DEFAULTS, margins = NO_CELL_MARGINS) {
243
216
  const props = tcPr === null ? EMPTY_TC_PR : parseProps(tcPr);
244
217
  if (!props) return null;
245
- const change = childChange(edit, props, defaults);
246
- if (!change) return null;
247
- const rendered = renderProps(
248
- setPropsChild(props, change.name, change.xml, TC_PR_ORDER)
249
- );
218
+ const edited = editedProps(edit, props, defaults);
219
+ if (!edited) return null;
220
+ const rendered = renderProps(edited);
250
221
  const next = rendered === "" ? null : rendered;
251
222
  if (next === tcPr) return null;
252
223
  return { tcPr: next, format: readCellProps(next, defaults, margins) };
253
224
  }
254
225
  function drawsOwnCellBorder(tcPr) {
255
- const el = tcPr === null ? null : parsePropsXml(tcPr);
256
- const borders = el ? childByLocalName(el, "tcBorders") : null;
226
+ const props = tcPr === null ? null : parseProps(tcPr);
227
+ const borders = props && containerOf(props, "tcBorders");
257
228
  if (!borders) return false;
258
229
  return ALL_CELL_SIDES.some(
259
- (side) => SIDE_NAMES[side].some((name) => drawsLine(childByLocalName(borders, name)))
230
+ (side) => SIDE_NAMES[side].some((name) => {
231
+ const element = childElement(borders, name);
232
+ return element !== null && drawsLine(element);
233
+ })
260
234
  );
261
235
  }
262
236
  function editRowHeight(trPr, heightPt) {
263
- const twips = Math.round(heightPt * 20);
264
- if (!Number.isFinite(heightPt) || heightPt <= 0 || !Number.isSafeInteger(twips)) {
265
- return null;
266
- }
267
- const props = trPr === null ? { tag: "w:trPr", attrs: null, children: [] } : parseProps(trPr);
237
+ const twips = heightPt > 0 ? ST_TwipsMeasure.format(Math.round(heightPt * TWIPS_PER_PT)) : null;
238
+ if (twips === null) return null;
239
+ const props = trPr === null ? { tag: wName("trPr"), attrs: null, children: [] } : parseProps(trPr);
268
240
  if (!props) return null;
269
- const current = propsChild(props.children, "trHeight")?.xml ?? null;
270
- const element = current === null ? null : parsePropsXml(current);
271
- if (current !== null && !element) return null;
272
- const writtenRule = element ? wAttr(element, "hRule") : null;
273
- const attrs = setAttr(attrPairs(element), "trHeight", "val", `${twips}`);
241
+ const current = childElement(props, "trHeight");
242
+ if (!current) return null;
243
+ const writtenRule = wAttrValue(current.attrs, "hRule");
244
+ const attrs = setAttr(current.attrs, "trHeight", "val", twips);
274
245
  const nextAttrs = setAttr(
275
246
  attrs,
276
247
  "trHeight",
277
248
  "hRule",
278
249
  writtenRule === "exact" ? "exact" : "atLeast"
279
250
  );
280
- const child = elementXml(element?.nodeName ?? wName("trHeight"), nextAttrs);
281
- const rendered = renderProps(
282
- setPropsChild(props, "trHeight", child, TR_PR_ORDER)
283
- );
251
+ const child = elementXml(current.tag ?? wName("trHeight"), nextAttrs);
252
+ const rendered = renderProps(setChild(props, "trHeight", child));
284
253
  if (rendered === trPr) return null;
285
254
  const parsed = parsePropsXml(rendered);
286
255
  const format = parsed ? readRowFormat(parsed) : null;
@@ -3,14 +3,19 @@
3
3
  * collapsed-border resolution matches OOXML inheritance without changing exported XML.
4
4
  */
5
5
  import type { CellFormat, CellMargins, InsideBorders, RowFormat, TableFormat, TableWidth } from "../../model/format";
6
+ import { type BorderLine } from "../../ooxml/units";
6
7
  export type { CellMargins, InsideBorders } from "../../model/format";
8
+ /** The parts of a table a table style formats conditionally (`w:tblStylePr/@w:type`, ST_TblStyleOverrideType) */
9
+ export type TableStyleOverrideType = "wholeTable" | "firstRow" | "lastRow" | "firstCol" | "lastCol" | "band1Vert" | "band2Vert" | "band1Horz" | "band2Horz" | "neCell" | "nwCell" | "seCell" | "swCell";
7
10
  /**
8
11
  * The width written down by `<w:tblW>` or `<w:tcW>`.
9
12
  * If we cannot make out what it means it is null, and such a width goes back out unchanged on export.
10
13
  *
11
- * `auto` and `nil` leave the width to the layout, so whatever number stands beside them says nothing.
14
+ * `auto` and `nil` leave the width to the layout unless the value carries an explicit percentage.
12
15
  * A percentage may be written in fiftieths of a percent, as in `w:w="2500"`, or as `w:w="50%"`.
13
16
  * Both have to be gathered into the same unit, or the table collapses into a thin strip on screen.
17
+ * Word lets an explicit `%` override `w:type` (MS-OI29500 §2.1.185(b)); see
18
+ * spec/notes/simpleTypes.md. A universal measure under `pct` remains unreadable here.
14
19
  */
15
20
  export declare function readTableWidth(parent: Element | null, name: "tblW" | "tcW"): TableWidth | null;
16
21
  /** The sequence of column widths (dxa) set by `<w:tblGrid>`. Revision markup (tblGridChange) is skipped */
@@ -32,12 +37,17 @@ export declare function readCellMarginsOf(parent: Element | null, name: "tblCell
32
37
  * their cell padding from.
33
38
  */
34
39
  export declare function layerCellMargins(style: CellMargins, direct: CellMargins): CellMargins;
35
- /** The lines a cell falls back on, one per side, already resolved for its spot in the grid */
40
+ /**
41
+ * The lines a cell falls back on, one per side, already resolved for its spot in the grid.
42
+ *
43
+ * They are held as the markup records them rather than as CSS, because a cell that takes one of
44
+ * them over as a border of its own writes it back into `w:tcBorders`.
45
+ */
36
46
  export interface CellBorderDefaults {
37
- top: string | null;
38
- bottom: string | null;
39
- left: string | null;
40
- right: string | null;
47
+ top: BorderLine | null;
48
+ bottom: BorderLine | null;
49
+ left: BorderLine | null;
50
+ right: BorderLine | null;
41
51
  }
42
52
  export declare const NO_BORDER_DEFAULTS: CellBorderDefaults;
43
53
  /** Which sides of a cell lie on the edge of the table's grid */
@@ -1,31 +1,43 @@
1
1
  // src/docx/tableFormatting/reading.ts
2
+ import { parsePropsXml } from "../../ooxml/props.js";
3
+ import {
4
+ ST_MeasurementOrPercent,
5
+ ST_SignedTwipsMeasure,
6
+ ST_TwipsMeasure
7
+ } from "../../ooxml/simpleTypes.js";
2
8
  import {
3
9
  ALIGN_BY_JC,
10
+ borderLineCss,
11
+ borderLineOfCss,
4
12
  borderSide,
5
13
  childValue,
6
14
  isOn,
7
15
  shadingOf,
8
- toNumber,
9
16
  twipsToPt,
10
17
  wAttr
11
18
  } from "../../ooxml/units.js";
12
19
  import { childByLocalName } from "../../ooxml/xml.js";
13
- import { parsePropsXml } from "../propsXml.js";
20
+ var FIFTIETHS_PER_PERCENT = 50;
14
21
  function readTableWidth(parent, name) {
15
22
  const el = parent ? childByLocalName(parent, name) : null;
16
23
  if (!el) return null;
17
24
  const type = wAttr(el, "type") ?? "dxa";
25
+ const width = ST_MeasurementOrPercent.parse(wAttr(el, "w"));
26
+ if (width?.kind === "percent") {
27
+ const fiftieths = Math.round(width.value * FIFTIETHS_PER_PERCENT);
28
+ return Number.isFinite(fiftieths) ? { type: "pct", fiftieths } : null;
29
+ }
18
30
  if (type === "auto") return { type: "auto" };
19
31
  if (type === "nil") return { type: "nil" };
20
- const raw = wAttr(el, "w");
21
- const value = toNumber(raw);
22
- if (value === null) return null;
23
- if (type === "dxa") return { type: "dxa", twips: value };
32
+ if (width === null) return null;
33
+ if (type === "dxa") {
34
+ return { type: "dxa", twips: width.value };
35
+ }
24
36
  if (type === "pct") {
25
- const isPercentText = raw?.endsWith("%") === true;
37
+ if (width.kind === "twips") return null;
26
38
  return {
27
39
  type: "pct",
28
- fiftieths: isPercentText ? Math.round(value * 50) : value
40
+ fiftieths: width.value
29
41
  };
30
42
  }
31
43
  return null;
@@ -35,7 +47,7 @@ function readGridCols(tblGrid) {
35
47
  const cols = [];
36
48
  for (const child of Array.from(tblGrid.children)) {
37
49
  if (child.localName !== "gridCol") continue;
38
- const w = toNumber(wAttr(child, "w"));
50
+ const w = ST_TwipsMeasure.parse(wAttr(child, "w"));
39
51
  if (w !== null) cols.push(w);
40
52
  }
41
53
  return cols;
@@ -69,7 +81,7 @@ function marginSide(margins, names) {
69
81
  if (!el) continue;
70
82
  const type = wAttr(el, "type") ?? "dxa";
71
83
  if (type === "nil") return 0;
72
- return type === "dxa" ? twipsToPt(wAttr(el, "w")) : null;
84
+ return type === "dxa" ? twipsToPt(ST_TwipsMeasure.parse(wAttr(el, "w"))) : null;
73
85
  }
74
86
  return null;
75
87
  }
@@ -107,10 +119,18 @@ function gridEdgesOf(rect, grid) {
107
119
  }
108
120
  function cellBorderDefaults(edges, outer, inside) {
109
121
  return {
110
- top: edges.top ? outer?.borderTop ?? null : inside.horizontal,
111
- bottom: edges.bottom ? outer?.borderBottom ?? null : inside.horizontal,
112
- left: edges.left ? outer?.borderLeft ?? null : inside.vertical,
113
- right: edges.right ? outer?.borderRight ?? null : inside.vertical
122
+ top: borderLineOfCss(
123
+ edges.top ? outer?.borderTop ?? null : inside.horizontal
124
+ ),
125
+ bottom: borderLineOfCss(
126
+ edges.bottom ? outer?.borderBottom ?? null : inside.horizontal
127
+ ),
128
+ left: borderLineOfCss(
129
+ edges.left ? outer?.borderLeft ?? null : inside.vertical
130
+ ),
131
+ right: borderLineOfCss(
132
+ edges.right ? outer?.borderRight ?? null : inside.vertical
133
+ )
114
134
  };
115
135
  }
116
136
  function tblStyleIdOf(tblPr) {
@@ -133,12 +153,12 @@ function readTableFormat(tblPr) {
133
153
  const align = ALIGN_BY_JC[childValue(tblPr, "jc") ?? ""];
134
154
  if (align) format.align = align;
135
155
  const tblInd = childByLocalName(tblPr, "tblInd");
136
- const indentLeftPt = tblInd ? twipsToPt(wAttr(tblInd, "w")) : null;
156
+ const indentLeftPt = tblInd ? twipsToPt(ST_SignedTwipsMeasure.parse(wAttr(tblInd, "w"))) : null;
137
157
  if (indentLeftPt !== null) format.indentLeftPt = indentLeftPt;
138
158
  return format;
139
159
  }
140
160
  function readRowHeight(trHeight) {
141
- const pt = twipsToPt(wAttr(trHeight, "val"));
161
+ const pt = twipsToPt(ST_TwipsMeasure.parse(wAttr(trHeight, "val")));
142
162
  if (pt === null || pt <= 0) return null;
143
163
  const rule = wAttr(trHeight, "hRule") === "exact" ? "exact" : "atLeast";
144
164
  return { rule, pt };
@@ -170,13 +190,13 @@ function readCellPadding(tcPr, margins) {
170
190
  function readCellFormat(tcPr, defaults, margins = NO_CELL_MARGINS) {
171
191
  const borders = tcPr ? childByLocalName(tcPr, "tcBorders") : null;
172
192
  const format = {};
173
- const top = borderSide(borders, "top") ?? defaults.top;
193
+ const top = borderSide(borders, "top") ?? borderLineCss(defaults.top);
174
194
  if (top) format.borderTop = top;
175
- const bottom = borderSide(borders, "bottom") ?? defaults.bottom;
195
+ const bottom = borderSide(borders, "bottom") ?? borderLineCss(defaults.bottom);
176
196
  if (bottom) format.borderBottom = bottom;
177
- const left = borderSide(borders, "left") ?? borderSide(borders, "start") ?? defaults.left;
197
+ const left = borderSide(borders, "left") ?? borderSide(borders, "start") ?? borderLineCss(defaults.left);
178
198
  if (left) format.borderLeft = left;
179
- const right = borderSide(borders, "right") ?? borderSide(borders, "end") ?? defaults.right;
199
+ const right = borderSide(borders, "right") ?? borderSide(borders, "end") ?? borderLineCss(defaults.right);
180
200
  if (right) format.borderRight = right;
181
201
  if (tcPr) {
182
202
  const background = shadingOf(tcPr);
@@ -1,9 +1,9 @@
1
1
  // src/docx/tableTemplate.ts
2
2
  import { elementXml } from "../ooxml/element.js";
3
3
  import { wName } from "../ooxml/names.js";
4
+ import { orderedElement, parsePropsXml } from "../ooxml/props.js";
4
5
  import { docxSchema } from "../schema/index.js";
5
6
  import { A4_PORTRAIT, bodyWidth } from "./pageGeometry.js";
6
- import { parsePropsXml } from "./propsXml.js";
7
7
  import {
8
8
  cellBorderDefaults,
9
9
  gridEdgesOf,
@@ -35,14 +35,21 @@ function borderXml(side) {
35
35
  ]);
36
36
  }
37
37
  function tablePropsXml(width) {
38
- const borders = BORDER_SIDES.map(borderXml).join("");
39
- return elementXml(
38
+ const borders = orderedElement(
39
+ wName("tblBorders"),
40
+ [],
41
+ BORDER_SIDES.map((side) => ({ name: side, xml: borderXml(side) }))
42
+ );
43
+ return orderedElement(
40
44
  wName("tblPr"),
41
45
  [],
42
46
  [
43
- widthXml("tblW", width),
44
- elementXml(wName("tblBorders"), [], [borders]),
45
- elementXml(wName("tblLayout"), [[wName("type"), "fixed"]])
47
+ { name: "tblW", xml: widthXml("tblW", width) },
48
+ { name: "tblBorders", xml: borders },
49
+ {
50
+ name: "tblLayout",
51
+ xml: elementXml(wName("tblLayout"), [[wName("type"), "fixed"]])
52
+ }
46
53
  ]
47
54
  );
48
55
  }
@@ -53,7 +60,11 @@ function widthXml(name, width) {
53
60
  ]);
54
61
  }
55
62
  function cellPropsXml(width) {
56
- return elementXml(wName("tcPr"), [], [widthXml("tcW", width)]);
63
+ return orderedElement(
64
+ wName("tcPr"),
65
+ [],
66
+ [{ name: "tcW", xml: widthXml("tcW", width) }]
67
+ );
57
68
  }
58
69
  function evenGridCols(cols, total) {
59
70
  const base = Math.floor(total / cols);
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import type { Node as PMNode } from "prosemirror-model";
8
8
  import type { DocxEditorHandle } from "./DocxEditor";
9
+ import type { ExportProblem } from "./docx/invariants";
9
10
  export declare const DOCX_MIME_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
10
11
  export interface DownloadDocxOptions {
11
12
  /** The name to save under. The `.docx` suffix may be there or not */
@@ -19,6 +20,9 @@ export type DownloadDocxResult = {
19
20
  status: "unavailable";
20
21
  } | {
21
22
  status: "empty";
23
+ } | {
24
+ status: "blocked";
25
+ problems: readonly ExportProblem[];
22
26
  };
23
27
  /** Settles the end of the name on exactly one `.docx` */
24
28
  export declare function withDocxExtension(fileName: string): string;
@@ -48,10 +52,9 @@ export declare function hasExportableContent(doc: PMNode): boolean;
48
52
  * Downloads the edited document as a docx file.
49
53
  *
50
54
  * `unavailable` when the editor is not mounted yet or the document could not be
51
- * opened, `empty` when there is nothing to export, and `exported` once a file has
52
- * gone out.
53
- * If writing the document back is blocked (a new list in a document without
54
- * numbering.xml, and the like), it is not passed over silently: `DocxExportError`
55
- * is rethrown as is.
55
+ * opened, `empty` when there is nothing to export, `blocked` with every reason
56
+ * when the document cannot be written back (a new list in a document without
57
+ * numbering.xml, and the like), and `exported` once a file has gone out.
58
+ * A refusal the check did not foresee is still thrown as `DocxExportError`.
56
59
  */
57
60
  export declare function downloadDocx(editor: DocxEditorHandle | null | undefined, options: DownloadDocxOptions): DownloadDocxResult;
package/dist/download.js CHANGED
@@ -29,6 +29,8 @@ function hasExportableContent(doc) {
29
29
  function downloadDocx(editor, options) {
30
30
  if (!editor) return { status: "unavailable" };
31
31
  if (!hasExportableContent(editor.view.state.doc)) return { status: "empty" };
32
+ const problems = editor.exportProblems();
33
+ if (problems.length > 0) return { status: "blocked", problems };
32
34
  const bytes = editor.exportBytes();
33
35
  const fileName = withDocxExtension(options.fileName);
34
36
  downloadBlob(createDocxBlob(bytes), fileName);