@stll/folio-core 0.22.1 → 0.22.3

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 (62) hide show
  1. package/dist/ai-edits/headless.d.ts +6 -6
  2. package/dist/ai-edits/headless.js +2 -2
  3. package/dist/content-controls/errors.d.ts +17 -17
  4. package/dist/content-controls/errors.js +3 -3
  5. package/dist/controller/layoutPipeline.js +28 -0
  6. package/dist/document-operations.d.ts +6 -6
  7. package/dist/document-operations.js +2 -2
  8. package/dist/docx/encryption/errors.d.ts +3 -3
  9. package/dist/docx/encryption/errors.js +1 -1
  10. package/dist/docx/ensureParaIds.d.ts +4 -4
  11. package/dist/docx/ensureParaIds.js +1 -1
  12. package/dist/docx/imageParser.js +6 -9
  13. package/dist/docx/metadataPrivacy.d.ts +6 -6
  14. package/dist/docx/metadataPrivacy.js +2 -2
  15. package/dist/docx/parser.d.ts +5 -5
  16. package/dist/docx/parser.js +1 -1
  17. package/dist/docx/server/applyDocxXmlPatchProposal.d.ts +6 -6
  18. package/dist/docx/server/applyDocxXmlPatchProposal.js +2 -2
  19. package/dist/docx/server/boundedArchive.d.ts +4 -4
  20. package/dist/docx/server/boundedArchive.js +1 -1
  21. package/dist/docx/server/build.d.ts +5 -5
  22. package/dist/docx/server/build.js +1 -1
  23. package/dist/docx/server/createBilingualDocument.d.ts +3 -3
  24. package/dist/docx/server/createBilingualDocument.js +1 -1
  25. package/dist/docx/server/evaluateDocxXmlPatchProposal.d.ts +6 -6
  26. package/dist/docx/server/evaluateDocxXmlPatchProposal.js +2 -2
  27. package/dist/docx/server/inspectDocxPackage.d.ts +3 -3
  28. package/dist/docx/server/inspectDocxPackage.js +9 -8
  29. package/dist/docx/styleParser.js +16 -16
  30. package/dist/docx/xmlParser.d.ts +7 -3
  31. package/dist/docx/xmlParser.js +20 -7
  32. package/dist/docx/xmlResourceLimits.d.ts +4 -4
  33. package/dist/docx/xmlResourceLimits.js +1 -1
  34. package/dist/layout-engine/measure/font-metrics.worker.js +1 -1
  35. package/dist/layout-engine/measure/measureHelpers.d.ts +2 -1
  36. package/dist/layout-engine/measure/measureHelpers.js +5 -2
  37. package/dist/layout-engine/types.d.ts +6 -0
  38. package/dist/layout-painter/anchoredImagePosition.d.ts +2 -1
  39. package/dist/layout-painter/anchoredImagePosition.js +14 -6
  40. package/dist/layout-painter/renderPage.js +11 -1
  41. package/dist/layout-painter/renderParagraph.js +3 -4
  42. package/dist/prosemirror/attrs/index.js +6 -5
  43. package/dist/prosemirror/commands/propertyChangeScope.d.ts +1 -1
  44. package/dist/prosemirror/commands/propertyChangeScope.js +1 -0
  45. package/dist/prosemirror/conversion/fromProseDoc.js +13 -3
  46. package/dist/prosemirror/conversion/toProseDoc.js +1 -0
  47. package/dist/prosemirror/extensions/core/ParagraphExtension.js +30 -3
  48. package/dist/prosemirror/extensions/marks/UnderlineExtension.js +9 -7
  49. package/dist/prosemirror/extensions/marks/markUtils.d.ts +2 -1
  50. package/dist/prosemirror/extensions/marks/markUtils.js +92 -16
  51. package/dist/prosemirror/plugins/selectionTracker.js +10 -5
  52. package/dist/prosemirror/schema/nodes.d.ts +1 -0
  53. package/dist/prosemirror/selectionMarks.d.ts +11 -0
  54. package/dist/prosemirror/selectionMarks.js +19 -0
  55. package/dist/prosemirror/selectionState.js +8 -20
  56. package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
  57. package/dist/redline.d.ts +4 -4
  58. package/dist/redline.js +1 -1
  59. package/dist/utils/formatToStyle.js +1 -1
  60. package/dist/version-comparison.d.ts +3 -3
  61. package/dist/version-comparison.js +1 -1
  62. package/package.json +3 -3
@@ -2,7 +2,7 @@ import { mergeParagraphFormatting } from "../utils/paragraphFormattingMerge.js";
2
2
  import { mergeTextFormatting } from "../utils/textFormattingMerge.js";
3
3
  import { BorderStyleSchema, ConditionalStyleTypeSchema, EmphasisMarkSchema, FontHintSchema, FontThemeSchema, HighlightColorSchema, LineSpacingRuleSchema, ParagraphAlignmentSchema, ShadingPatternSchema, StyleTypeSchema, TabLeaderSchema, TabStopAlignmentSchema, TableCellTextDirectionSchema, TableRowHeightRuleSchema, TableWidthTypeSchema, TextEffectSchema, ThemeColorSlotSchema, UnderlineStyleSchema, narrowEnum } from "./parserEnums.js";
4
4
  import { resolveThemeFontRef } from "./themeParser.js";
5
- import { findChild, findChildren, getAttribute, getLocalName, parseBooleanElement, parseNumberingLevelAttribute, parseNumericAttribute, parseTableMeasurementValue, parseXmlDocument } from "./xmlParser.js";
5
+ import { findChild, findChildren, getAttribute, getLocalName, parseBooleanElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffValue, parseTableMeasurementValue, parseXmlDocument } from "./xmlParser.js";
6
6
  //#region src/docx/styleParser.ts
7
7
  /**
8
8
  * Parse text formatting properties (w:rPr)
@@ -224,9 +224,9 @@ function parseBorderSpec(border) {
224
224
  const space = parseNumericAttribute(border, "w", "space");
225
225
  if (space !== void 0) spec.space = space;
226
226
  const shadowAttr = getAttribute(border, "w", "shadow");
227
- if (shadowAttr) spec.shadow = shadowAttr === "1" || shadowAttr === "true";
227
+ if (shadowAttr) spec.shadow = parseOnOffValue(shadowAttr) ?? false;
228
228
  const frame = getAttribute(border, "w", "frame");
229
- if (frame) spec.frame = frame === "1" || frame === "true";
229
+ if (frame) spec.frame = parseOnOffValue(frame) ?? false;
230
230
  return spec;
231
231
  }
232
232
  /**
@@ -278,9 +278,9 @@ function parseParagraphProperties(pPr, theme) {
278
278
  const lineRule = narrowEnum(getAttribute(spacing, "w", "lineRule"), LineSpacingRuleSchema);
279
279
  if (lineRule) formatting.lineSpacingRule = lineRule;
280
280
  const beforeAuto = getAttribute(spacing, "w", "beforeAutospacing");
281
- if (beforeAuto) formatting.beforeAutospacing = beforeAuto === "1" || beforeAuto === "true";
281
+ if (beforeAuto) formatting.beforeAutospacing = parseOnOffValue(beforeAuto) ?? false;
282
282
  const afterAuto = getAttribute(spacing, "w", "afterAutospacing");
283
- if (afterAuto) formatting.afterAutospacing = afterAuto === "1" || afterAuto === "true";
283
+ if (afterAuto) formatting.afterAutospacing = parseOnOffValue(afterAuto) ?? false;
284
284
  }
285
285
  const ind = findChild(pPr, "w", "ind");
286
286
  if (ind) {
@@ -452,17 +452,17 @@ function parseTableLook(tblLook) {
452
452
  }
453
453
  }
454
454
  const firstColumn = getAttribute(tblLook, "w", "firstColumn");
455
- if (firstColumn) look.firstColumn = firstColumn === "1";
455
+ if (firstColumn) look.firstColumn = parseOnOffValue(firstColumn) ?? false;
456
456
  const firstRow = getAttribute(tblLook, "w", "firstRow");
457
- if (firstRow) look.firstRow = firstRow === "1";
457
+ if (firstRow) look.firstRow = parseOnOffValue(firstRow) ?? false;
458
458
  const lastColumn = getAttribute(tblLook, "w", "lastColumn");
459
- if (lastColumn) look.lastColumn = lastColumn === "1";
459
+ if (lastColumn) look.lastColumn = parseOnOffValue(lastColumn) ?? false;
460
460
  const lastRow = getAttribute(tblLook, "w", "lastRow");
461
- if (lastRow) look.lastRow = lastRow === "1";
461
+ if (lastRow) look.lastRow = parseOnOffValue(lastRow) ?? false;
462
462
  const noHBand = getAttribute(tblLook, "w", "noHBand");
463
- if (noHBand) look.noHBand = noHBand === "1";
463
+ if (noHBand) look.noHBand = parseOnOffValue(noHBand) ?? false;
464
464
  const noVBand = getAttribute(tblLook, "w", "noVBand");
465
- if (noVBand) look.noVBand = noVBand === "1";
465
+ if (noVBand) look.noVBand = parseOnOffValue(noVBand) ?? false;
466
466
  return Object.keys(look).length > 0 ? look : void 0;
467
467
  }
468
468
  /**
@@ -669,7 +669,7 @@ function parseStyle(styleEl, theme) {
669
669
  type: narrowEnum(rawType, StyleTypeSchema) ?? "paragraph"
670
670
  };
671
671
  const defaultAttr = getAttribute(styleEl, "w", "default");
672
- if (defaultAttr) style.default = defaultAttr === "1" || defaultAttr === "true";
672
+ if (defaultAttr) style.default = parseOnOffValue(defaultAttr) ?? false;
673
673
  const children = collectStyleChildren(styleEl);
674
674
  const nameEl = children.name;
675
675
  if (nameEl) {
@@ -871,10 +871,10 @@ function parseStyleDefinitionsFromDocument(doc, theme, resolvedStyles) {
871
871
  const latentStylesEl = findChild(doc, "w", "latentStyles");
872
872
  if (latentStylesEl) {
873
873
  const latentStyles = {
874
- defLockedState: getAttribute(latentStylesEl, "w", "defLockedState") === "1",
875
- defSemiHidden: getAttribute(latentStylesEl, "w", "defSemiHidden") === "1",
876
- defUnhideWhenUsed: getAttribute(latentStylesEl, "w", "defUnhideWhenUsed") === "1",
877
- defQFormat: getAttribute(latentStylesEl, "w", "defQFormat") === "1"
874
+ defLockedState: parseOnOffValue(getAttribute(latentStylesEl, "w", "defLockedState")) ?? false,
875
+ defSemiHidden: parseOnOffValue(getAttribute(latentStylesEl, "w", "defSemiHidden")) ?? false,
876
+ defUnhideWhenUsed: parseOnOffValue(getAttribute(latentStylesEl, "w", "defUnhideWhenUsed")) ?? false,
877
+ defQFormat: parseOnOffValue(getAttribute(latentStylesEl, "w", "defQFormat")) ?? false
878
878
  };
879
879
  const defUIPriority = parseNumericAttribute(latentStylesEl, "w", "defUIPriority");
880
880
  if (defUIPriority !== void 0) latentStyles.defUIPriority = defUIPriority;
@@ -272,13 +272,17 @@ declare function parseNumberingLevelAttribute(element: XmlElement | null | undef
272
272
  * (`5000`); normalize those to the ECMA-376 unit the layout engine expects.
273
273
  */
274
274
  declare function parseTableMeasurementValue(element: XmlElement | null | undefined, widthType: string): number | undefined;
275
+ /**
276
+ * Parse an OOXML `ST_OnOff` lexical value.
277
+ */
278
+ declare function parseOnOffValue(value: string | null | undefined): boolean | undefined;
275
279
  /**
276
280
  * Parse a boolean value from an attribute or element presence
277
281
  *
278
282
  * OOXML boolean conventions:
279
283
  * - Element presence with no val attribute = true
280
- * - w:val="true" or w:val="1" = true
281
- * - w:val="false" or w:val="0" = false
284
+ * - w:val="true", w:val="1", or w:val="on" = true
285
+ * - w:val="false", w:val="0", or w:val="off" = false
282
286
  *
283
287
  * @param element - Element to check
284
288
  * @param namespace - Namespace for val attribute
@@ -334,4 +338,4 @@ declare function mergeXmlnsDeclarations(inherited: Record<string, string>, eleme
334
338
  */
335
339
  declare function cloneWithXmlnsDeclarations(element: XmlElement, xmlnsDecls: Record<string, string>): XmlElement;
336
340
  //#endregion
337
- export { NAMESPACES, WORDPROCESSINGML_NAMESPACE_URIS, XmlElement, XmlNamespaceScope, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseTableMeasurementValue, parseXml, parseXmlDocument };
341
+ export { NAMESPACES, WORDPROCESSINGML_NAMESPACE_URIS, XmlElement, XmlNamespaceScope, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument };
@@ -438,8 +438,7 @@ function getTextContent(element) {
438
438
  function hasFlag(element, namespace, name) {
439
439
  const value = getAttribute(element, namespace, name);
440
440
  if (value === null) return false;
441
- if (value === "0" || value === "false" || value === "off") return false;
442
- return true;
441
+ return parseOnOffValue(value) ?? true;
443
442
  }
444
443
  /**
445
444
  * Check if a child element exists (used for boolean flags in OOXML)
@@ -515,12 +514,27 @@ function parseTableMeasurementValue(element, widthType) {
515
514
  return Number.isNaN(num) ? void 0 : num;
516
515
  }
517
516
  /**
517
+ * Parse an OOXML `ST_OnOff` lexical value.
518
+ */
519
+ function parseOnOffValue(value) {
520
+ if (value === null || value === void 0) return;
521
+ switch (value) {
522
+ case "1":
523
+ case "true":
524
+ case "on": return true;
525
+ case "0":
526
+ case "false":
527
+ case "off": return false;
528
+ default: return;
529
+ }
530
+ }
531
+ /**
518
532
  * Parse a boolean value from an attribute or element presence
519
533
  *
520
534
  * OOXML boolean conventions:
521
535
  * - Element presence with no val attribute = true
522
- * - w:val="true" or w:val="1" = true
523
- * - w:val="false" or w:val="0" = false
536
+ * - w:val="true", w:val="1", or w:val="on" = true
537
+ * - w:val="false", w:val="0", or w:val="off" = false
524
538
  *
525
539
  * @param element - Element to check
526
540
  * @param namespace - Namespace for val attribute
@@ -541,8 +555,7 @@ function parseBooleanElement(element, namespace = "w") {
541
555
  }
542
556
  }
543
557
  if (val === null) return true;
544
- if (val === "0" || val === "false" || val === "off") return false;
545
- return true;
558
+ return parseOnOffValue(val) ?? true;
546
559
  }
547
560
  /**
548
561
  * Deep find - search recursively for an element
@@ -699,4 +712,4 @@ function cloneWithXmlnsDeclarations(element, xmlnsDecls) {
699
712
  return element;
700
713
  }
701
714
  //#endregion
702
- export { NAMESPACES, WORDPROCESSINGML_NAMESPACE_URIS, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseTableMeasurementValue, parseXml, parseXmlDocument };
715
+ export { NAMESPACES, WORDPROCESSINGML_NAMESPACE_URIS, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument };
@@ -11,12 +11,12 @@ type XmlResourceLimits = {
11
11
  maxNodes: number;
12
12
  };
13
13
  type XmlResourceLimitKind = "bytes" | "depth" | "nodes" | "syntax";
14
- declare const XmlResourceLimitError_base: import("better-result").TaggedErrorClass<"XmlResourceLimitError", {
14
+ declare const XmlResourceLimitError_base: import("better-result").TaggedErrorClass<"XmlResourceLimitError">;
15
+ /** XML input exceeded a parser resource bound or could not be scanned safely. */
16
+ declare class XmlResourceLimitError extends XmlResourceLimitError_base<{
15
17
  message: string;
16
18
  limit: XmlResourceLimitKind;
17
- }>;
18
- /** XML input exceeded a parser resource bound or could not be scanned safely. */
19
- declare class XmlResourceLimitError extends XmlResourceLimitError_base {}
19
+ }> {}
20
20
  /**
21
21
  * Bound XML bytes, element count, and nesting before building an object tree.
22
22
  * The lexical scan is iterative, so deeply nested input cannot consume the JS
@@ -6,7 +6,7 @@ const FOLIO_XML_RESOURCE_LIMITS = {
6
6
  maxNodes: 1e6
7
7
  };
8
8
  /** XML input exceeded a parser resource bound or could not be scanned safely. */
9
- var XmlResourceLimitError = class extends TaggedError("XmlResourceLimitError")() {};
9
+ var XmlResourceLimitError = class extends TaggedError("XmlResourceLimitError") {};
10
10
  const exceedsUtf8ByteLimit = (value, maxBytes) => {
11
11
  let bytes = 0;
12
12
  for (let index = 0; index < value.length; index += 1) {
@@ -17,7 +17,7 @@ import { TaggedError } from "better-result";
17
17
  * that touches `document`, `window`, or React. The protocol module is
18
18
  * deliberately dependency-free.
19
19
  */
20
- var WorkerInitError = class extends TaggedError("WorkerInitError")() {};
20
+ var WorkerInitError = class extends TaggedError("WorkerInitError") {};
21
21
  let ctx = null;
22
22
  let initError = null;
23
23
  const FONT_FINGERPRINT_EPSILON = .01;
@@ -3,6 +3,7 @@ import { FontStyle } from "./measureTypes.js";
3
3
  //#region src/layout-engine/measure/measureHelpers.d.ts
4
4
  declare const DEFAULT_FONT_SIZE = 11;
5
5
  declare const DEFAULT_FONT_FAMILY = "Calibri";
6
+ declare const DOCX_SCRIPT_FONT_SCALE = 0.75;
6
7
  /**
7
8
  * Build a measurement `FontStyle` from a run's formatting. Single source of
8
9
  * truth for run → FontStyle so every measurement path (layout line-breaking,
@@ -87,4 +88,4 @@ declare function halfPtToPx(halfPt: number): number;
87
88
  */
88
89
  declare function pxToHalfPt(px: number): number;
89
90
  //#endregion
90
- export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, buildFontString, buildRunFontStyle, clearFontResolvedCache, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
91
+ export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, DOCX_SCRIPT_FONT_SCALE, buildFontString, buildRunFontStyle, clearFontResolvedCache, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
@@ -9,12 +9,14 @@ import { FONT_KERNING_MODE, getRunFontKerningMode } from "./textMeasurementPolic
9
9
  * char-offset geometry. The layout engine imports these directly so it never
10
10
  * transitively pulls the canvas measurement backend; the canvas implementation
11
11
  * (`measureContainer.ts`) consumes them too.
12
+ * Script sizing: eigenpal/docx-editor@585413d0 (Apache-2.0), modified for Folio.
12
13
  */
13
14
  const TWIPS_PER_INCH = 1440;
14
15
  const PX_PER_INCH = 96;
15
16
  const TWIPS_PER_PX = TWIPS_PER_INCH / PX_PER_INCH;
16
17
  const DEFAULT_FONT_SIZE = 11;
17
18
  const DEFAULT_FONT_FAMILY = "Calibri";
19
+ const DOCX_SCRIPT_FONT_SCALE = .75;
18
20
  /**
19
21
  * Build a measurement `FontStyle` from a run's formatting. Single source of
20
22
  * truth for run → FontStyle so every measurement path (layout line-breaking,
@@ -24,7 +26,8 @@ const DEFAULT_FONT_FAMILY = "Calibri";
24
26
  * and size fallbacks for runs that declare neither.
25
27
  */
26
28
  function buildRunFontStyle(run, fallbackFontFamily, fallbackFontSize) {
27
- const fontSize = run.fontSize ?? fallbackFontSize;
29
+ const baseFontSize = run.fontSize ?? fallbackFontSize;
30
+ const fontSize = run.superscript || run.subscript ? baseFontSize * DOCX_SCRIPT_FONT_SCALE : baseFontSize;
28
31
  return {
29
32
  fontFamily: run.fontFamily ?? fallbackFontFamily,
30
33
  ...run.eastAsiaFontFamily !== void 0 ? { eastAsiaFontFamily: run.eastAsiaFontFamily } : {},
@@ -168,4 +171,4 @@ function pxToHalfPt(px) {
168
171
  return pxToPt(px) * 2;
169
172
  }
170
173
  //#endregion
171
- export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, buildFontString, buildRunFontStyle, clearFontResolvedCache, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
174
+ export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, DOCX_SCRIPT_FONT_SCALE, buildFontString, buildRunFontStyle, clearFontResolvedCache, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
@@ -1063,6 +1063,12 @@ type Page = {
1063
1063
  fragments: Fragment[];
1064
1064
  /** Page margins. */
1065
1065
  margins: PageMargins;
1066
+ /**
1067
+ * Authored section margins before header/footer clearance expands the body
1068
+ * content box insets. Page-margin-relative anchors use these page-setup
1069
+ * landmarks instead of furniture-expanded content insets.
1070
+ */
1071
+ authoredMargins?: PageMargins;
1066
1072
  /** Page size (width, height). */
1067
1073
  size: {
1068
1074
  w: number;
@@ -1,4 +1,4 @@
1
- import { ImageRun } from "../layout-engine/types.js";
1
+ import { ImageRun, PageMargins } from "../layout-engine/types.js";
2
2
  //#region src/layout-painter/anchoredImagePosition.d.ts
3
3
  type PageGeometry = {
4
4
  pageWidth: number;
@@ -7,6 +7,7 @@ type PageGeometry = {
7
7
  marginTop: number;
8
8
  marginRight: number;
9
9
  marginBottom: number;
10
+ authoredMargins?: PageMargins;
10
11
  contentWidth: number;
11
12
  contentHeight: number;
12
13
  };
@@ -1,6 +1,13 @@
1
1
  import { emuToPixels } from "./renderUtils.js";
2
2
  //#region src/layout-painter/anchoredImagePosition.ts
3
+ const authoredMargins = (geometry) => geometry.authoredMargins ?? {
4
+ top: geometry.marginTop,
5
+ right: geometry.marginRight,
6
+ bottom: geometry.marginBottom,
7
+ left: geometry.marginLeft
8
+ };
3
9
  function resolveHorizontalBand(relativeTo, geometry) {
10
+ const margins = authoredMargins(geometry);
4
11
  switch (relativeTo) {
5
12
  case "page": return {
6
13
  baseX: -geometry.marginLeft,
@@ -9,12 +16,12 @@ function resolveHorizontalBand(relativeTo, geometry) {
9
16
  case "leftMargin":
10
17
  case "insideMargin": return {
11
18
  baseX: -geometry.marginLeft,
12
- bandWidth: geometry.marginLeft
19
+ bandWidth: margins.left
13
20
  };
14
21
  case "rightMargin":
15
22
  case "outsideMargin": return {
16
- baseX: geometry.contentWidth,
17
- bandWidth: geometry.marginRight
23
+ baseX: geometry.pageWidth - geometry.marginLeft - margins.right,
24
+ bandWidth: margins.right
18
25
  };
19
26
  case "character": return {
20
27
  baseX: 0,
@@ -27,6 +34,7 @@ function resolveHorizontalBand(relativeTo, geometry) {
27
34
  }
28
35
  }
29
36
  function resolveVerticalBand(relativeTo, fragmentY, geometry) {
37
+ const margins = authoredMargins(geometry);
30
38
  switch (relativeTo) {
31
39
  case "page": return {
32
40
  baseY: -geometry.marginTop,
@@ -34,11 +42,11 @@ function resolveVerticalBand(relativeTo, fragmentY, geometry) {
34
42
  };
35
43
  case "topMargin": return {
36
44
  baseY: -geometry.marginTop,
37
- bandHeight: geometry.marginTop
45
+ bandHeight: margins.top
38
46
  };
39
47
  case "bottomMargin": return {
40
- baseY: geometry.contentHeight,
41
- bandHeight: geometry.marginBottom
48
+ baseY: geometry.pageHeight - geometry.marginTop - margins.bottom,
49
+ bandHeight: margins.bottom
42
50
  };
43
51
  case "paragraph":
44
52
  case "line": return {
@@ -797,6 +797,7 @@ function renderPage(page, context, options = {}) {
797
797
  marginTop: page.margins.top,
798
798
  marginRight: page.margins.right,
799
799
  marginBottom: page.margins.bottom,
800
+ ...page.authoredMargins ? { authoredMargins: page.authoredMargins } : {},
800
801
  contentWidth,
801
802
  contentHeight: page.size.h - page.margins.top - page.margins.bottom
802
803
  };
@@ -1209,10 +1210,19 @@ function computePageFingerprint(page, blockLookup) {
1209
1210
  function computePageRenderFingerprint(page, blockLookup) {
1210
1211
  return computePageFingerprintInternal(page, blockLookup, { includePmPositions: false });
1211
1212
  }
1213
+ const pageMarginsFingerprint = (margins) => [
1214
+ margins.top,
1215
+ margins.right,
1216
+ margins.bottom,
1217
+ margins.left,
1218
+ margins.header ?? "",
1219
+ margins.footer ?? ""
1220
+ ].join(",");
1212
1221
  function computePageFingerprintInternal(page, blockLookup, options) {
1213
1222
  const parts = [];
1214
1223
  parts.push(`s:${page.size.w},${page.size.h}`);
1215
- parts.push(`m:${page.margins.top},${page.margins.right},${page.margins.bottom},${page.margins.left}`);
1224
+ parts.push(`m:${pageMarginsFingerprint(page.margins)}`);
1225
+ if (page.authoredMargins) parts.push(`am:${pageMarginsFingerprint(page.authoredMargins)}`);
1216
1226
  parts.push(`n:${page.number}`);
1217
1227
  if (page.sectionIndex !== void 0) parts.push(`si:${page.sectionIndex}`);
1218
1228
  if (page.sectionPageNumber !== void 0) parts.push(`sp:${page.sectionPageNumber}`);
@@ -2,7 +2,7 @@ import { ommlToMathml } from "../docx/mathToMathml.js";
2
2
  import { parseXmlDocument } from "../docx/xmlParser.js";
3
3
  import { evaluateFieldInstruction } from "../fields/evaluateField.js";
4
4
  import { getListMarkerInlineWidth, getListMarkerVisualOffset } from "../layout-engine/measure/listMarkerWidth.js";
5
- import "../layout-engine/measure/measureHelpers.js";
5
+ import { DOCX_SCRIPT_FONT_SCALE } from "../layout-engine/measure/measureHelpers.js";
6
6
  import { FONT_KERNING_MODE, countCompressibleSpaces, getFontKerningMode, getRunFontKerningMode, toPaintedText } from "../layout-engine/measure/textMeasurementPolicy.js";
7
7
  import { isFloatingImageRun } from "../layout-engine/types.js";
8
8
  import { calculateTabWidth } from "../prosemirror/utils/tabCalculator.js";
@@ -76,7 +76,6 @@ function isMathRun(run) {
76
76
  }
77
77
  const AUTOMATIC_TEXT_COLOR_VALUES = /* @__PURE__ */ new Set(["auto", "windowtext"]);
78
78
  const DEFAULT_BLACK_TEXT_COLOR_VALUES = /* @__PURE__ */ new Set(["000000", "000"]);
79
- const DOCX_SUPERSCRIPT_SCALE = .75;
80
79
  const SUGGESTION_COLOR_CSS = "var(--suggestion-color, #6d3bd6)";
81
80
  const SUGGESTION_TINT_CSS = "var(--suggestion-bg, color-mix(in oklch, #6d3bd6 12%, transparent))";
82
81
  const SUGGESTION_TINT_LAYER_CSS = `linear-gradient(${SUGGESTION_TINT_CSS}, ${SUGGESTION_TINT_CSS})`;
@@ -109,8 +108,8 @@ function fontSizePtToPx(fontSizePt) {
109
108
  return fontSizePt * 96 / 72;
110
109
  }
111
110
  function getRaisedRunFontSize(run) {
112
- if (run.fontSize) return `${fontSizePtToPx(run.fontSize) * DOCX_SUPERSCRIPT_SCALE}px`;
113
- return `${DOCX_SUPERSCRIPT_SCALE}em`;
111
+ if (run.fontSize) return `${fontSizePtToPx(run.fontSize) * DOCX_SCRIPT_FONT_SCALE}px`;
112
+ return `${DOCX_SCRIPT_FONT_SCALE}em`;
114
113
  }
115
114
  /**
116
115
  * Apply text run styles to an element
@@ -1,4 +1,4 @@
1
- import { FIELD_TYPE_VALUES, HIGHLIGHT_COLOR_VALUES, IMAGE_HORIZONTAL_ALIGNMENT_VALUES, IMAGE_HORIZONTAL_RELATIVE_TO_VALUES, IMAGE_VERTICAL_ALIGNMENT_VALUES, IMAGE_VERTICAL_RELATIVE_TO_VALUES, IMAGE_WRAP_TEXT_VALUES, IMAGE_WRAP_TYPE_VALUES, LINE_SPACING_RULE_VALUES, OUTLINE_STYLE_ATTR_VALUES, PARAGRAPH_ALIGNMENT_VALUES, POSITIONAL_TAB_ALIGNMENT_VALUES, POSITIONAL_TAB_LEADER_VALUES, POSITIONAL_TAB_RELATIVE_TO_VALUES, SDT_LOCK_VALUES, SDT_TYPE_VALUES, SHADING_PATTERN_VALUES, TABLE_CELL_TEXT_DIRECTION_VALUES, TABLE_CELL_VERTICAL_ALIGNMENT_VALUES, TABLE_JUSTIFICATION_VALUES, TABLE_ROW_HEIGHT_RULE_VALUES, TABLE_WIDTH_TYPE_VALUES, TAB_LEADER_VALUES, TAB_STOP_ALIGNMENT_VALUES, TEXT_EFFECT_VALUES, THEME_COLOR_SLOT_VALUES, UNDERLINE_STYLE_VALUES } from "../../types/documentEnumValues.js";
1
+ import { FIELD_TYPE_VALUES, FONT_THEME_VALUES, HIGHLIGHT_COLOR_VALUES, IMAGE_HORIZONTAL_ALIGNMENT_VALUES, IMAGE_HORIZONTAL_RELATIVE_TO_VALUES, IMAGE_VERTICAL_ALIGNMENT_VALUES, IMAGE_VERTICAL_RELATIVE_TO_VALUES, IMAGE_WRAP_TEXT_VALUES, IMAGE_WRAP_TYPE_VALUES, LINE_SPACING_RULE_VALUES, OUTLINE_STYLE_ATTR_VALUES, PARAGRAPH_ALIGNMENT_VALUES, POSITIONAL_TAB_ALIGNMENT_VALUES, POSITIONAL_TAB_LEADER_VALUES, POSITIONAL_TAB_RELATIVE_TO_VALUES, SDT_LOCK_VALUES, SDT_TYPE_VALUES, SHADING_PATTERN_VALUES, TABLE_CELL_TEXT_DIRECTION_VALUES, TABLE_CELL_VERTICAL_ALIGNMENT_VALUES, TABLE_JUSTIFICATION_VALUES, TABLE_ROW_HEIGHT_RULE_VALUES, TABLE_WIDTH_TYPE_VALUES, TAB_LEADER_VALUES, TAB_STOP_ALIGNMENT_VALUES, TEXT_EFFECT_VALUES, THEME_COLOR_SLOT_VALUES, UNDERLINE_STYLE_VALUES } from "../../types/documentEnumValues.js";
2
2
  import { isParagraphDirection } from "../paragraphDirection.js";
3
3
  import { TRACKED_CHANGE_PROVENANCE_VALUES } from "../schema/marks.js";
4
4
  import { panic } from "better-result";
@@ -163,6 +163,7 @@ const readParagraphAttrs = (node) => {
163
163
  optionalNumber(attrs, "spaceBefore", "paragraph.attrs.spaceBefore", issues);
164
164
  optionalNumber(attrs, "spaceAfter", "paragraph.attrs.spaceAfter", issues);
165
165
  optionalNumber(attrs, "lineSpacing", "paragraph.attrs.lineSpacing", issues);
166
+ optionalBoolean(attrs, "lineSpacingExplicit", "paragraph.attrs.lineSpacingExplicit", issues);
166
167
  optionalBoolean(attrs, "snapToGrid", "paragraph.attrs.snapToGrid", issues);
167
168
  optionalOneOf(attrs, "lineSpacingRule", "paragraph.attrs.lineSpacingRule", issues, LINE_SPACING_RULE_VALUES);
168
169
  optionalNumber(attrs, "indentLeft", "paragraph.attrs.indentLeft", issues);
@@ -571,10 +572,10 @@ const readFontFamilyMarkAttrs = (mark) => {
571
572
  optionalString(attrs, "eastAsia", "fontFamily.attrs.eastAsia", issues);
572
573
  optionalString(attrs, "cs", "fontFamily.attrs.cs", issues);
573
574
  optionalString(attrs, "hint", "fontFamily.attrs.hint", issues);
574
- optionalString(attrs, "asciiTheme", "fontFamily.attrs.asciiTheme", issues);
575
- optionalString(attrs, "hAnsiTheme", "fontFamily.attrs.hAnsiTheme", issues);
576
- optionalString(attrs, "eastAsiaTheme", "fontFamily.attrs.eastAsiaTheme", issues);
577
- optionalString(attrs, "csTheme", "fontFamily.attrs.csTheme", issues);
575
+ optionalOneOf(attrs, "asciiTheme", "fontFamily.attrs.asciiTheme", issues, FONT_THEME_VALUES);
576
+ optionalOneOf(attrs, "hAnsiTheme", "fontFamily.attrs.hAnsiTheme", issues, FONT_THEME_VALUES);
577
+ optionalOneOf(attrs, "eastAsiaTheme", "fontFamily.attrs.eastAsiaTheme", issues, FONT_THEME_VALUES);
578
+ optionalOneOf(attrs, "csTheme", "fontFamily.attrs.csTheme", issues, FONT_THEME_VALUES);
578
579
  return attrsResult(attrs, issues);
579
580
  };
580
581
  const expectFontFamilyMarkAttrs = (mark) => expectCachedMarkAttrs(mark, fontFamilyAttrsCache, readFontFamilyMarkAttrs, "font family attrs");
@@ -19,7 +19,7 @@ type AttrPatch = Record<string, unknown>;
19
19
  * without a style resolver: `numPrFromStyle`, the `list*` rendering attrs,
20
20
  * `spacingFromDocDefaults`, `spacingFromImplicitDefaultStyle`
21
21
  */
22
- declare const PPR_CHANGE_SCOPED_ATTR_KEYS: readonly ["styleId", "numPr", "alignment", "spaceBefore", "spaceAfter", "lineSpacing", "lineSpacingRule", "snapToGrid", "spacingExplicit", "indentLeft", "indentRight", "indentFirstLine", "hangingIndent", "borders", "shading", "tabs", "pageBreakBefore", "keepNext", "keepLines", "widowControl", "contextualSpacing", "outlineLevel", "direction", "_autospacingBase"];
22
+ declare const PPR_CHANGE_SCOPED_ATTR_KEYS: readonly ["styleId", "numPr", "alignment", "spaceBefore", "spaceAfter", "lineSpacing", "lineSpacingRule", "lineSpacingExplicit", "snapToGrid", "spacingExplicit", "indentLeft", "indentRight", "indentFirstLine", "hangingIndent", "borders", "shading", "tabs", "pageBreakBefore", "keepNext", "keepLines", "widowControl", "contextualSpacing", "outlineLevel", "direction", "_autospacingBase"];
23
23
  /**
24
24
  * Build the attr patch that rejecting one pPrChange applies to a paragraph:
25
25
  * every in-scope key set to the stored previous value, or reset to `null`
@@ -27,6 +27,7 @@ const PPR_CHANGE_SCOPED_ATTR_KEYS = [
27
27
  "spaceAfter",
28
28
  "lineSpacing",
29
29
  "lineSpacingRule",
30
+ "lineSpacingExplicit",
30
31
  "snapToGrid",
31
32
  "spacingExplicit",
32
33
  "indentLeft",
@@ -543,6 +543,7 @@ function paragraphAttrsToFormatting(attrs) {
543
543
  const afterAutospacingEdited = afterHasAutospacingBase ? !autospacingMatchesBase(attrs._autospacingBase, "after", spaceAfter) : afterOriginalAutospacing && attrs._autospacingBase == null;
544
544
  const shouldSerializeSpaceBefore = typeof spaceBefore === "number" && (!beforeHasAutospacingBase || beforeAutospacingEdited);
545
545
  const shouldSerializeSpaceAfter = typeof spaceAfter === "number" && (!afterHasAutospacingBase || afterAutospacingEdited);
546
+ const hasDirectLineSpacing = attrs.lineSpacingExplicit === true;
546
547
  if (attrs._originalFormatting) {
547
548
  const orig = attrs._originalFormatting;
548
549
  const result = { ...orig };
@@ -556,6 +557,15 @@ function paragraphAttrsToFormatting(attrs) {
556
557
  if (typeof spaceAfter === "number") result.spaceAfter = spaceAfter;
557
558
  else delete result.spaceAfter;
558
559
  }
560
+ if (attrs.spacingExplicit?.before && typeof spaceBefore === "number") result.spaceBefore = spaceBefore;
561
+ if (attrs.spacingExplicit?.after && typeof spaceAfter === "number") result.spaceAfter = spaceAfter;
562
+ const originalHasDirectLineSpacing = orig.lineSpacing !== void 0 || orig.lineSpacingRule !== void 0;
563
+ if (hasDirectLineSpacing || originalHasDirectLineSpacing) {
564
+ if (typeof attrs.lineSpacing === "number") result.lineSpacing = attrs.lineSpacing;
565
+ else Reflect.deleteProperty(result, "lineSpacing");
566
+ if (attrs.lineSpacingRule) result.lineSpacingRule = attrs.lineSpacingRule;
567
+ else Reflect.deleteProperty(result, "lineSpacingRule");
568
+ }
559
569
  if (attrs.alignment !== (orig.alignment ?? void 0)) if (attrs.alignment) result.alignment = attrs.alignment;
560
570
  else delete result.alignment;
561
571
  if (isStyleSourcedNumPr(attrs)) {
@@ -583,15 +593,15 @@ function paragraphAttrsToFormatting(attrs) {
583
593
  }
584
594
  const outlineLevel = Reflect.get(attrs, "outlineLevel");
585
595
  const bidi = directionToBidi(attrs.direction);
586
- if (!(attrs.alignment || shouldSerializeSpaceBefore || shouldSerializeSpaceAfter || beforeAutospacingEdited || afterAutospacingEdited || attrs.lineSpacing || attrs.snapToGrid != null || attrs.indentLeft || attrs.indentRight || attrs.indentFirstLine || attrs.numPr || attrs.styleId || attrs.borders || attrs.shading || attrs.tabs || typeof outlineLevel === "number" || attrs.contextualSpacing || attrs.spacingExplicit || bidi != null || attrs.pageBreakBefore != null || attrs.widowControl != null || attrs.kinsoku != null || attrs.overflowPunctuation != null || attrs.suppressAutoHyphens != null)) return;
596
+ if (!(attrs.alignment || shouldSerializeSpaceBefore || shouldSerializeSpaceAfter || beforeAutospacingEdited || afterAutospacingEdited || hasDirectLineSpacing || attrs.snapToGrid != null || attrs.indentLeft || attrs.indentRight || attrs.indentFirstLine || attrs.numPr || attrs.styleId || attrs.borders || attrs.shading || attrs.tabs || typeof outlineLevel === "number" || attrs.contextualSpacing || attrs.spacingExplicit || bidi != null || attrs.pageBreakBefore != null || attrs.widowControl != null || attrs.kinsoku != null || attrs.overflowPunctuation != null || attrs.suppressAutoHyphens != null)) return;
587
597
  const f = {};
588
598
  if (attrs.alignment) f.alignment = attrs.alignment;
589
599
  if (shouldSerializeSpaceBefore) f.spaceBefore = spaceBefore;
590
600
  if (beforeAutospacingEdited) f.beforeAutospacing = false;
591
601
  if (shouldSerializeSpaceAfter) f.spaceAfter = spaceAfter;
592
602
  if (afterAutospacingEdited) f.afterAutospacing = false;
593
- if (attrs.lineSpacing) f.lineSpacing = attrs.lineSpacing;
594
- if (attrs.lineSpacingRule) f.lineSpacingRule = attrs.lineSpacingRule;
603
+ if (hasDirectLineSpacing && typeof attrs.lineSpacing === "number") f.lineSpacing = attrs.lineSpacing;
604
+ if (hasDirectLineSpacing && attrs.lineSpacingRule) f.lineSpacingRule = attrs.lineSpacingRule;
595
605
  if (attrs.snapToGrid != null) f.snapToGrid = attrs.snapToGrid;
596
606
  if (attrs.spacingExplicit) f.spacingExplicit = attrs.spacingExplicit;
597
607
  if (attrs.indentLeft) f.indentLeft = attrs.indentLeft;
@@ -319,6 +319,7 @@ function paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOver
319
319
  set("spaceAfter", formatting?.spaceAfter ?? stylePpr?.spaceAfter);
320
320
  set("lineSpacing", formatting?.lineSpacing ?? stylePpr?.lineSpacing);
321
321
  set("lineSpacingRule", formatting?.lineSpacingRule ?? stylePpr?.lineSpacingRule);
322
+ set("lineSpacingExplicit", formatting?.lineSpacing !== void 0 || formatting?.lineSpacingRule !== void 0 ? true : void 0);
322
323
  set("snapToGrid", formatting?.snapToGrid ?? stylePpr?.snapToGrid);
323
324
  set("spacingExplicit", formatting?.spacingExplicit);
324
325
  const paragraphStyle = styleId ? styleResolver.getStyle(styleId) ?? styleResolver.getDefaultParagraphStyle() : styleResolver.getDefaultParagraphStyle();
@@ -161,6 +161,7 @@ function extractParagraphAttrsFromStyle(element) {
161
161
  if (spacing) {
162
162
  attrs.lineSpacing = spacing.lineSpacing;
163
163
  attrs.lineSpacingRule = spacing.lineSpacingRule;
164
+ attrs.lineSpacingExplicit = true;
164
165
  }
165
166
  }
166
167
  if (style.marginTop) {
@@ -187,6 +188,7 @@ const paragraphNodeSpec = {
187
188
  spaceAfter: { default: null },
188
189
  lineSpacing: { default: null },
189
190
  lineSpacingRule: { default: null },
191
+ lineSpacingExplicit: { default: null },
190
192
  snapToGrid: { default: null },
191
193
  spacingExplicit: { default: null },
192
194
  spacingFromDocDefaults: { default: null },
@@ -323,6 +325,30 @@ function setParagraphAttr(attr, value) {
323
325
  return true;
324
326
  };
325
327
  }
328
+ function setParagraphSpacingAttr(side, twips) {
329
+ return (state, dispatch) => {
330
+ const { $from, $to } = state.selection;
331
+ if (!dispatch) return true;
332
+ let tr = state.tr;
333
+ const seen = /* @__PURE__ */ new Set();
334
+ state.doc.nodesBetween($from.pos, $to.pos, (node, pos) => {
335
+ if (node.type.name === "paragraph" && !seen.has(pos)) {
336
+ seen.add(pos);
337
+ const spacingExplicit = {
338
+ ...node.attrs["spacingExplicit"],
339
+ [side]: true
340
+ };
341
+ tr = tr.setNodeMarkup(pos, void 0, {
342
+ ...node.attrs,
343
+ [side === "before" ? "spaceBefore" : "spaceAfter"]: twips,
344
+ spacingExplicit
345
+ });
346
+ }
347
+ });
348
+ dispatch(tr.scrollIntoView());
349
+ return true;
350
+ };
351
+ }
326
352
  function setParagraphAttrsCmd(attrs) {
327
353
  return (state, dispatch) => {
328
354
  const { $from, $to } = state.selection;
@@ -348,7 +374,8 @@ function makeSetAlignment(alignment) {
348
374
  function makeSetLineSpacing(value, rule = "auto") {
349
375
  return (state, dispatch) => setParagraphAttrsCmd({
350
376
  lineSpacing: value,
351
- lineSpacingRule: rule
377
+ lineSpacingRule: rule,
378
+ lineSpacingExplicit: true
352
379
  })(state, dispatch);
353
380
  }
354
381
  function makeIncreaseIndent(amount = 720) {
@@ -498,8 +525,8 @@ const ParagraphExtension = createNodeExtension({
498
525
  singleSpacing: () => makeSetLineSpacing(240),
499
526
  oneAndHalfSpacing: () => makeSetLineSpacing(360),
500
527
  doubleSpacing: () => makeSetLineSpacing(480),
501
- setSpaceBefore: (twips) => setParagraphAttr("spaceBefore", twips),
502
- setSpaceAfter: (twips) => setParagraphAttr("spaceAfter", twips),
528
+ setSpaceBefore: (twips) => setParagraphSpacingAttr("before", twips),
529
+ setSpaceAfter: (twips) => setParagraphSpacingAttr("after", twips),
503
530
  increaseIndent: (amount) => makeIncreaseIndent(amount),
504
531
  decreaseIndent: (amount) => makeDecreaseIndent(amount),
505
532
  setIndentLeft: (twips) => setParagraphAttr("indentLeft", twips > 0 ? twips : null),
@@ -1,8 +1,7 @@
1
1
  import { expectUnderlineMarkAttrs } from "../../attrs/index.js";
2
2
  import { createMarkExtension } from "../create.js";
3
- import { setMark } from "./markUtils.js";
3
+ import { setMark, toggleUnderlineMark } from "./markUtils.js";
4
4
  import { panic } from "better-result";
5
- import { toggleMark } from "prosemirror-commands";
6
5
  //#region src/prosemirror/extensions/marks/UnderlineExtension.ts
7
6
  /**
8
7
  * Underline Mark Extension
@@ -17,14 +16,17 @@ const UnderlineExtension = createMarkExtension({
17
16
  },
18
17
  parseDOM: [{ tag: "u" }, {
19
18
  style: "text-decoration",
20
- getAttrs: (value) => value.includes("underline") ? {} : false
19
+ getAttrs: (value) => {
20
+ if (value.includes("underline")) return {};
21
+ return value.includes("none") ? { style: "none" } : false;
22
+ }
21
23
  }],
22
24
  toDOM(mark) {
23
25
  const attrs = expectUnderlineMarkAttrs(mark);
24
26
  const style = attrs.style;
25
27
  const colorRgb = attrs.color?.rgb;
26
- const cssStyle = ["text-decoration: underline"];
27
- if (style && style !== "single") {
28
+ const cssStyle = [`text-decoration: ${style === "none" ? "none" : "underline"}`];
29
+ if (style && style !== "single" && style !== "none") {
28
30
  const cssDecorationStyle = {
29
31
  double: "double",
30
32
  dotted: "dotted",
@@ -46,13 +48,13 @@ const UnderlineExtension = createMarkExtension({
46
48
  if (!underlineType) panic("Missing mark type: underline");
47
49
  return {
48
50
  commands: {
49
- toggleUnderline: () => toggleMark(underlineType),
51
+ toggleUnderline: () => toggleUnderlineMark(underlineType),
50
52
  setUnderlineStyle: (style, color) => setMark(underlineType, {
51
53
  style,
52
54
  color
53
55
  })
54
56
  },
55
- keyboardShortcuts: { "Mod-u": toggleMark(underlineType) }
57
+ keyboardShortcuts: { "Mod-u": toggleUnderlineMark(underlineType) }
56
58
  };
57
59
  }
58
60
  });
@@ -4,6 +4,7 @@ import { Mark, MarkType, Schema } from "prosemirror-model";
4
4
  //#region src/prosemirror/extensions/marks/markUtils.d.ts
5
5
  type MarkAttrs = Record<string, unknown>;
6
6
  declare function setMark(markType: MarkType, attrs: MarkAttrs): Command;
7
+ declare function toggleUnderlineMark(markType: MarkType): Command;
7
8
  declare function removeMark(markType: MarkType): Command;
8
9
  /**
9
10
  * Check if a mark is active in the current selection
@@ -30,4 +31,4 @@ declare function createSetMarkCommand(markType: MarkType, attrs?: Record<string,
30
31
  */
31
32
  declare function createRemoveMarkCommand(markType: MarkType): Command;
32
33
  //#endregion
33
- export { clearFormatting, createRemoveMarkCommand, createSetMarkCommand, getMarkAttr, isMarkActive, removeMark, setMark, textFormattingToMarks };
34
+ export { clearFormatting, createRemoveMarkCommand, createSetMarkCommand, getMarkAttr, isMarkActive, removeMark, setMark, textFormattingToMarks, toggleUnderlineMark };