@stll/folio-core 0.41.0 → 0.43.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 (49) hide show
  1. package/README.md +16 -0
  2. package/dist/compat/eigenpal.d.ts +3 -3
  3. package/dist/controller/layoutPipeline.d.ts +2 -1
  4. package/dist/controller/layoutPipeline.js +5 -2
  5. package/dist/controller/layoutSession.d.ts +2 -1
  6. package/dist/controller/layoutSession.js +1 -0
  7. package/dist/docx/blockContentParser.js +2 -1
  8. package/dist/docx/commentIdNormalization.d.ts +8 -0
  9. package/dist/docx/commentIdNormalization.js +16 -0
  10. package/dist/docx/defaultParagraphStyle.d.ts +22 -0
  11. package/dist/docx/defaultParagraphStyle.js +32 -0
  12. package/dist/docx/footnoteParser.js +2 -0
  13. package/dist/docx/headerFooterRefParser.d.ts +12 -1
  14. package/dist/docx/headerFooterRefParser.js +9 -2
  15. package/dist/docx/numberingParser.js +2 -1
  16. package/dist/docx/numberingReference.d.ts +21 -0
  17. package/dist/docx/numberingReference.js +21 -0
  18. package/dist/docx/numberingReferenceNormalization.d.ts +11 -2
  19. package/dist/docx/numberingReferenceNormalization.js +32 -9
  20. package/dist/docx/paragraphParser.js +3 -2
  21. package/dist/docx/parser.js +10 -2
  22. package/dist/docx/rezip.d.ts +20 -3
  23. package/dist/docx/rezip.js +46 -18
  24. package/dist/docx/server/createBilingualDocument.js +5 -4
  25. package/dist/docx/styleParser.js +2 -2
  26. package/dist/index.d.ts +3 -3
  27. package/dist/internal/pageBreakRunSourceDescendantIndex.d.ts +2 -0
  28. package/dist/internal/pageBreakRunSourceDescendantIndex.js +7 -5
  29. package/dist/layout-bridge/convert/templatePreviewFlow.d.ts +19 -11
  30. package/dist/layout-bridge/convert/templatePreviewFlow.js +103 -38
  31. package/dist/layout-bridge/convert/toFlowBlocks.js +12 -3
  32. package/dist/prosemirror/commands/pageBreak.js +12 -1
  33. package/dist/prosemirror/conversion/fromProseDoc.js +2 -1
  34. package/dist/prosemirror/conversion/toProseDoc.js +27 -12
  35. package/dist/prosemirror/extensions/features/ListExtension.js +42 -4
  36. package/dist/prosemirror/listMarker.js +2 -1
  37. package/dist/prosemirror/numberedRefFields.js +2 -1
  38. package/dist/prosemirror/pageBreakRunProjection.d.ts +11 -3
  39. package/dist/prosemirror/pageBreakRunProjection.js +16 -8
  40. package/dist/prosemirror/plugins/templatePreviewValues.d.ts +42 -1
  41. package/dist/prosemirror/plugins/templatePreviewValues.js +217 -14
  42. package/dist/prosemirror/styles/resolvedStyleAttrs.js +2 -1
  43. package/dist/prosemirror/styles/styleResolver.js +3 -6
  44. package/dist/prosemirror/utils/visualLineNavigation.d.ts +22 -2
  45. package/dist/prosemirror/utils/visualLineNavigation.js +80 -63
  46. package/dist/server.d.ts +2 -2
  47. package/dist/style-engine/styleEngine.d.ts +1 -1
  48. package/dist/style-sets/extract.js +57 -11
  49. package/package.json +2 -1
@@ -1,15 +1,17 @@
1
1
  import { consumeTrackedSectionEndpointRemoval } from "../internal/sectionEndpointResolution.js";
2
2
  import { consumeSectionReferenceResolution } from "../internal/sectionReferenceResolution.js";
3
3
  import { isAllowedExternalWatermarkImageUrl } from "../watermark/index.js";
4
- import { normalizeAppVersionInExtendedProperties } from "./appVersionNormalization.js";
4
+ import { appVersionInSchemaForm, normalizeAppVersionInExtendedProperties } from "./appVersionNormalization.js";
5
5
  import { withoutOrphanCommentRanges } from "./commentRangeIntegrity.js";
6
6
  import { applyReplyThreadMarkers } from "./commentReplyMarkers.js";
7
7
  import { normalizeDrawingIds } from "./drawingIdNormalization.js";
8
8
  import { rebindDrawingImageRelationship } from "./drawingRelationships.js";
9
9
  import { parseEndnotes, parseFootnotes } from "./footnoteParser.js";
10
+ import { parseHeaderFooterType } from "./headerFooterRefParser.js";
10
11
  import { assertValidFolioDocumentModel } from "./modelValidation.js";
11
12
  import { isNewDataUrlDrawing } from "./newImage.js";
12
13
  import { parseNumbering } from "./numberingParser.js";
14
+ import { isNumberingReference } from "./numberingReference.js";
13
15
  import { isUnsafePackagePath, reconcilePackageReferences, removeUnsafeEntries } from "./packageParts.js";
14
16
  import { normalizeParaIdRangeInXmlParts } from "./paraIdRangeNormalization.js";
15
17
  import { RELATIONSHIP_TYPES, parseRelationships, resolveRelativePath } from "./relsParser.js";
@@ -113,7 +115,7 @@ const extractHeaderFooterReferences = (xml) => {
113
115
  const rId = getAttributeByNamespaceUri(node, OFFICE_RELATIONSHIP_NAMESPACE_URIS, "id");
114
116
  if (rId) references.push({
115
117
  element,
116
- type: getAttributeByNamespaceUri(node, WORDPROCESSINGML_NAMESPACE_URIS, "type") ?? "default",
118
+ type: parseHeaderFooterType(getAttributeByNamespaceUri(node, WORDPROCESSINGML_NAMESPACE_URIS, "type")),
117
119
  rId
118
120
  });
119
121
  }
@@ -1524,14 +1526,24 @@ function isDocxBuffer(buffer) {
1524
1526
  return view[0] === 80 && view[1] === 75;
1525
1527
  }
1526
1528
  /**
1529
+ * The application version a newly created package states.
1530
+ *
1531
+ * `AppVersion` describes the application, so it is written only alongside one,
1532
+ * and it says nothing about the host beyond the `XX.YYYY` form the schema
1533
+ * fixes. Deriving it from the mapping every save exit already uses keeps the
1534
+ * two from drifting apart.
1535
+ */
1536
+ const CREATED_APP_VERSION = appVersionInSchemaForm("1");
1537
+ /**
1527
1538
  * Create a new empty DOCX file
1528
1539
  *
1540
+ * @param properties - Document properties the package states about itself
1529
1541
  * @returns Promise resolving to minimal DOCX as ArrayBuffer
1530
1542
  */
1531
- function createEmptyDocx() {
1532
- return generateDocxZip(createEmptyDocxZip(), 6);
1543
+ function createEmptyDocx(properties = {}) {
1544
+ return generateDocxZip(createEmptyDocxZip(properties), 6);
1533
1545
  }
1534
- const createEmptyDocxZip = () => {
1546
+ const createEmptyDocxZip = ({ creator, application }) => {
1535
1547
  const zip = new JSZip();
1536
1548
  zip.file("[Content_Types].xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1537
1549
  <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
@@ -1586,16 +1598,15 @@ const createEmptyDocxZip = () => {
1586
1598
  </w:style>
1587
1599
  </w:styles>`);
1588
1600
  const now = (/* @__PURE__ */ new Date()).toISOString();
1601
+ const creatorElement = creator === void 0 ? "" : `\n <dc:creator>${escapeXml(creator)}</dc:creator>`;
1589
1602
  zip.file("docProps/core.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1590
- <cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1591
- <dc:creator>EigenPal DOCX Editor</dc:creator>
1603
+ <cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">${creatorElement}
1592
1604
  <dcterms:created xsi:type="dcterms:W3CDTF">${now}</dcterms:created>
1593
1605
  <dcterms:modified xsi:type="dcterms:W3CDTF">${now}</dcterms:modified>
1594
1606
  </cp:coreProperties>`);
1607
+ const applicationElements = application === void 0 ? "" : `\n <Application>${escapeXml(application)}</Application>\n <AppVersion>${CREATED_APP_VERSION}</AppVersion>`;
1595
1608
  zip.file("docProps/app.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1596
- <Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">
1597
- <Application>EigenPal DOCX Editor</Application>
1598
- <AppVersion>1.0000</AppVersion>
1609
+ <Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">${applicationElements}
1599
1610
  </Properties>`);
1600
1611
  return zip;
1601
1612
  };
@@ -1603,9 +1614,11 @@ const createEmptyDocxZip = () => {
1603
1614
  * Create a new DOCX from a Document (without requiring original buffer)
1604
1615
  *
1605
1616
  * @param doc - Document to serialize
1617
+ * @param properties - Document properties a package built from scratch states
1618
+ * about itself; ignored when `doc` carries a source package
1606
1619
  * @returns Promise resolving to DOCX as ArrayBuffer
1607
1620
  */
1608
- async function createDocx(doc) {
1621
+ async function createDocx(doc, properties = {}) {
1609
1622
  if (doc.originalBuffer) {
1610
1623
  const source = await loadParsedZipSource(doc, doc.originalBuffer);
1611
1624
  return finishRepack({
@@ -1618,7 +1631,7 @@ async function createDocx(doc) {
1618
1631
  updateModifiedDate: true
1619
1632
  });
1620
1633
  }
1621
- const zip = await createDocumentSeedZip(doc);
1634
+ const zip = await createDocumentSeedZip(doc, properties);
1622
1635
  return finishRepack({
1623
1636
  document: withoutOrphanCommentRanges(doc),
1624
1637
  originalZip: zip,
@@ -1629,8 +1642,8 @@ async function createDocx(doc) {
1629
1642
  updateModifiedDate: true
1630
1643
  });
1631
1644
  }
1632
- const createDocumentSeedZip = async (doc) => {
1633
- const zip = createEmptyDocxZip();
1645
+ const createDocumentSeedZip = async (doc, properties) => {
1646
+ const zip = createEmptyDocxZip(properties);
1634
1647
  const relationships = ["<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles\" Target=\"styles.xml\"/>"];
1635
1648
  const overrides = [];
1636
1649
  let nextRelationshipId = 2;
@@ -1673,17 +1686,32 @@ const createDocumentSeedZip = async (doc) => {
1673
1686
  };
1674
1687
  const relationshipXml = (id, type, target) => `<Relationship Id="rId${id}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/${type}" Target="${target}"/>`;
1675
1688
  const overrideXml = (partName, contentType) => `<Override PartName="${partName}" ContentType="${contentType}"/>`;
1689
+ /**
1690
+ * A package this build assembles from a Document must define the numbering its
1691
+ * styles name: Word reads a styles.xml reference nothing resolves as no
1692
+ * numbering at all, silently dropping the style's list.
1693
+ *
1694
+ * The panic is an invariant over Folio's own output, not input validation. A
1695
+ * source file can carry a dangling style reference, and it never arrives here:
1696
+ * `normalizeStyleNumberingReferences` rewrites one to the "no numbering"
1697
+ * sentinel at every boundary that mints a Document from user styles (`parseDocx`
1698
+ * and the style-set extraction entry points). What reaches this point is what
1699
+ * Folio itself put in the model, so a miss is a defect in Folio.
1700
+ */
1676
1701
  const assertStyleNumberingReferences = (doc) => {
1677
1702
  const referenced = /* @__PURE__ */ new Set();
1678
1703
  for (const style of doc.package.styles?.styles ?? []) {
1679
1704
  const numId = style.pPr?.numPr?.numId;
1680
- if (numId !== void 0) referenced.add(numId);
1705
+ if (isNumberingReference(numId)) referenced.add(numId);
1681
1706
  }
1682
1707
  if (referenced.size === 0) return;
1683
- const available = new Set(doc.package.numbering?.nums.map((numbering) => numbering.numId) ?? []);
1684
- for (const numId of referenced) if (!available.has(numId)) panic(`Style references missing numbering definition ${numId}`);
1708
+ const available = new Map(doc.package.numbering?.nums.map((numbering) => [numbering.numId, numbering]) ?? []);
1685
1709
  const availableAbstract = new Set(doc.package.numbering?.abstractNums.map((numbering) => numbering.abstractNumId) ?? []);
1686
- for (const numbering of doc.package.numbering?.nums ?? []) if (!availableAbstract.has(numbering.abstractNumId)) panic(`Numbering definition ${numbering.numId} references missing abstract numbering`);
1710
+ for (const numId of referenced) {
1711
+ const numbering = available.get(numId);
1712
+ if (!numbering) panic(`Style references missing numbering definition ${numId}`);
1713
+ if (!availableAbstract.has(numbering.abstractNumId)) panic(`Numbering definition ${numbering.numId} references missing abstract numbering`);
1714
+ }
1687
1715
  };
1688
1716
  //#endregion
1689
1717
  export { COMMENTS_CONTENT_TYPE, COMMENTS_EXTENDED_CONTENT_TYPE, COMMENTS_EXTENDED_PART, COMMENTS_EXTENDED_PART_LOWER, DocxPackageFidelityError, addCommentsExtendedOverride, addCommentsExtendedRelationship, addMedia, addRelationship, applyUpdatesToZip, collectHeaderFooterUpdates, collectHyperlinksWithoutRId, createDocx, createEmptyDocx, findMaxRId, hasModelDrivenPictureWatermark, hasUnmaterializedHeaderFooter, hasUnmaterializedInlineResources, isDocxBuffer, notePartRelsPath, removeCommentsExtendedOverride, removeCommentsExtendedRelationship, repackDocx, repackDocxFromRaw, updateCoreProperties, updateDocumentXml, updateMultipleFiles, updateXmlFile, validateDocx, withoutAttachedTemplate };
@@ -1,4 +1,5 @@
1
1
  import { deterministicHexId } from "../../utils/hexId.js";
2
+ import { isNumberingReference } from "../numberingReference.js";
2
3
  import { getParagraphText } from "../paragraphParser.js";
3
4
  import { cloneParagraphWithoutPropertySource } from "../paragraphPropertySource.js";
4
5
  import { getRunText } from "../runParser.js";
@@ -257,7 +258,7 @@ const resolveInheritedOutlineLevel = (style, styleById) => {
257
258
  /** The numbering a paragraph renders with: direct `numPr`, else the style chain's. */
258
259
  const effectiveNumPr = (paragraph, styleById) => {
259
260
  const direct = paragraph.formatting?.numPr;
260
- if (direct?.numId !== void 0) return direct.numId === 0 ? void 0 : direct;
261
+ if (direct?.numId !== void 0) return isNumberingReference(direct.numId) ? direct : void 0;
261
262
  const styleId = paragraph.formatting?.styleId;
262
263
  return styleId ? styleNumPr(styleById.get(styleId), styleById) : void 0;
263
264
  };
@@ -267,7 +268,7 @@ const styleNumPr = (style, styleById) => {
267
268
  while (current && !seen.has(current.styleId)) {
268
269
  seen.add(current.styleId);
269
270
  const numPr = current.pPr?.numPr;
270
- if (numPr?.numId !== void 0) return numPr.numId === 0 ? void 0 : numPr;
271
+ if (numPr?.numId !== void 0) return isNumberingReference(numPr.numId) ? numPr : void 0;
271
272
  current = current.basedOn ? styleById.get(current.basedOn) : void 0;
272
273
  }
273
274
  };
@@ -396,11 +397,11 @@ const cloneParagraphForTarget = (paragraph, targetParaId, styleCloner, cloner, b
396
397
  const nextFormatting = formatting && {
397
398
  ...formatting,
398
399
  ...formatting.styleId !== void 0 && { styleId: styleCloner.styleIdFor(formatting.styleId) },
399
- ...formatting.numPr?.numId !== void 0 && formatting.numPr.numId !== 0 && { numPr: {
400
+ ...formatting.numPr !== void 0 && isNumberingReference(formatting.numPr.numId) && { numPr: {
400
401
  ...formatting.numPr,
401
402
  numId: cloner.cloneNumId(formatting.numPr.numId)
402
403
  } },
403
- ...formatting.numPrFromStyle?.numId !== void 0 && formatting.numPrFromStyle.numId !== 0 && { numPrFromStyle: {
404
+ ...formatting.numPrFromStyle !== void 0 && isNumberingReference(formatting.numPrFromStyle.numId) && { numPrFromStyle: {
404
405
  ...formatting.numPrFromStyle,
405
406
  numId: cloner.cloneNumId(formatting.numPrFromStyle.numId)
406
407
  } }
@@ -2,6 +2,7 @@ import { isValidHexColor } from "../utils/colorResolver.js";
2
2
  import { parseHorizontalScalePercent } from "../utils/horizontalScale.js";
3
3
  import { mergeParagraphFormatting } from "../utils/paragraphFormattingMerge.js";
4
4
  import { mergeStyleTextFormatting } from "../utils/textFormattingMerge.js";
5
+ import { resolveDefaultParagraphStyle } from "./defaultParagraphStyle.js";
5
6
  import { BorderStyleSchema, ConditionalStyleTypeSchema, EmphasisMarkSchema, FontHintSchema, FontThemeSchema, HighlightColorSchema, LineSpacingRuleSchema, ParagraphAlignmentSchema, ShadingPatternSchema, StyleTypeSchema, TabLeaderSchema, TabStopAlignmentSchema, TableCellTextDirectionSchema, TableRowHeightRuleSchema, TableWidthTypeSchema, TextEffectSchema, ThemeColorSlotSchema, UnderlineStyleSchema, narrowEnum } from "./parserEnums.js";
6
7
  import { resolveThemeFontRef } from "./themeParser.js";
7
8
  import { findChild, findChildren, getAttribute, getLocalName, parseBooleanElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffValue, parseTableMeasurementValue, parseXmlDocument } from "./xmlParser.js";
@@ -916,8 +917,7 @@ function getResolvedStyle(styleId, styleMap) {
916
917
  * Get the default paragraph style
917
918
  */
918
919
  function getDefaultParagraphStyle(styleMap) {
919
- for (const style of styleMap.values()) if (style.type === "paragraph" && style.default) return style;
920
- return styleMap.get("Normal");
920
+ return resolveDefaultParagraphStyle(styleMap.values());
921
921
  }
922
922
  /**
923
923
  * Get the default character style
package/dist/index.d.ts CHANGED
@@ -20,7 +20,7 @@ import { CreateEmptyDocumentOptions, createEmptyDocument } from "./utils/createD
20
20
  import { mergeDocumentContent } from "./utils/mergeDocumentContent.js";
21
21
  import { DocumentStyleCatalog, DocumentStyleCatalogEntry, ExtractDocumentStyleSetOptions, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, inspectDocumentStyles, inspectDocumentStylesFromDocx } from "./style-sets/extract.js";
22
22
  import { STELLA_STYLE_SET_NAME, createStellaStyleDocumentPreset, createStellaStyleSet } from "./style-sets/stellaStyle.js";
23
- import { createDocx } from "./docx/rezip.js";
23
+ import { DocumentPropertiesOptions, createDocx } from "./docx/rezip.js";
24
24
  import { DRAWING_SAFETY_CLASSES, DrawingSafetyClass } from "./docx/imageRawXml.js";
25
25
  import { DocxCompatibility, DocxCompatibilityContext, DocxCompatibilityIssue, DocxCompatibilityLocation, DocxCompatibilityPart, DocxDrawingClassification, FolioDocxCompatibilityHost, FolioDocxCompatibilityProfile, InspectDocxCompatibilityOptions, inspectDocxCompatibility } from "./docx/compatibility.js";
26
26
  import { BlockRect } from "./paged-layout/blockGeometry.js";
@@ -30,7 +30,7 @@ import { getFolioCaretViewportRect, getFolioSelectionViewportRect } from "./page
30
30
  import { AICitationRange, createAICitationDecorationsPlugin, setAICitationsMeta, setActiveCitationMeta } from "./prosemirror/plugins/aiCitationDecorations.js";
31
31
  import { AnonymizationMatch, AnonymizationTerm, anonymizationDecorationsKey, getAnonymizationMatches, setAnonymizationTermsMeta } from "./prosemirror/plugins/anonymizationDecorations.js";
32
32
  import { DirectiveKind, DirectiveRange, getTemplateDirectives, scanDirectives } from "./prosemirror/plugins/templateDirectives.js";
33
- import { TemplatePreviewSpan, TemplatePreviewValue, TemplatePreviewValues, setTemplatePreviewValues } from "./prosemirror/plugins/templatePreviewValues.js";
33
+ import { TemplatePreviewHiddenRange, TemplatePreviewSpan, TemplatePreviewValue, TemplatePreviewValues, setTemplatePreviewValues } from "./prosemirror/plugins/templatePreviewValues.js";
34
34
  import { TemplateSlashMenuKeyAction, TemplateSlashMenuState, clearTemplateSlashMenu, consumeTemplateSlashQuery, getTemplateSlashMenu, resetTemplateSlashQuery, templateSlashMenuKey } from "./prosemirror/plugins/templateSlashMenu.js";
35
35
  import { AcceptAutocompleteResult, AutocompleteSuggestionPluginOptions, AutocompleteSuggestionState, AutocompleteSuggestionStatus, AutocompleteTriggerCheck, AutocompleteTriggerOptions, AutocompleteTriggerSkipReason, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, acceptAutocompleteSuggestion, acceptAutocompleteWord, appendAutocompleteToken, autocompleteSuggestionKey, autocompleteSuggestionPlugin, clearAutocompleteSuggestion, finishAutocompleteSuggestion, getAutocompleteSuggestion, shouldTriggerAutocomplete, startAutocompleteSuggestion } from "./prosemirror/plugins/autocompleteSuggestion.js";
36
36
  import { ImageMeta, ImageRef, MarkdownOptions, MarkdownResult } from "./markdown/types.js";
@@ -41,4 +41,4 @@ import { getGoogleFontsEnabled, setEmbeddedFontFamilyMap, setGoogleFontsEnabled
41
41
  import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
42
42
  type Document = document_d_exports.Document;
43
43
  type DocxConformanceClass = document_d_exports.DocxConformanceClass;
44
- export { type AIBarStatus, type AIChatMode, type AICitation, type AICitationRange, type AICitationSource, type AIGenerateInput, type AISuggestion, type AISuggestionApplyMode, type AISuggestionPreset, type AISuggestionSeverity, type AISuggestionStatus, type AcceptAutocompleteResult, type AnonymizationMatch, type AnonymizationTerm, type ApplyFolioDocumentOperationsOptions, type ApplyResult, type AutocompleteSuggestionPluginOptions, type AutocompleteSuggestionState, type AutocompleteSuggestionStatus, type AutocompleteTriggerCheck, type AutocompleteTriggerOptions, type AutocompleteTriggerSkipReason, type BlockRect, COMPARE_REVISION_FORMATS, COMPARE_UNSUPPORTED_REASONS, COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS, type CompareChange, type CompareChangeLocation, type CompareCompatibility, type CompareContentOptions, CompareDocxApplyError, type CompareDocxError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, type CompareDocxOptions, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, type CompareFolioRequirement, type CompareResult, type CompareRevisionFormat, type CompareUnsupportedPart, type CompareUnsupportedReason, type CompareVerification, type CompareVerificationCause, type CompareVerificationFailure, type CompareVerificationInvariant, type CreateEmptyDocumentOptions, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DOCX_CONFORMANCE_CLASSES, DRAWING_SAFETY_CLASSES, type DeriveBlockIdInput, type DirectiveKind, type DirectiveRange, type Document, type DocumentPreset, type DocumentStyleCatalog, type DocumentStyleCatalogEntry, type DocumentStyleSet, type DocxCompatibility, type DocxCompatibilityContext, type DocxCompatibilityIssue, type DocxCompatibilityLocation, type DocxCompatibilityPart, type DocxConformanceClass, type DocxDrawingClassification, type DrawingSafetyClass, type EmbeddedFont, type EmbeddedFontParts, type ExtractDocumentStyleSetOptions, FOLIO_CONTENT_COMPARISON_LIMITS, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, type FinalParagraphMarkRevision, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIBlockStructuralBoundary, type FolioAIBlockTableLocation, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyOutcome, type FolioAIEditApplyResult, type FolioAIEditNormalization, type FolioAIEditNormalizationCode, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAIInlineBooleanProperty, type FolioAIInlineFormatting, type FolioAIInlineFormattingPatch, type FolioAIParagraphSpacing, type FolioAISignatureParty, type FolioBlockId, type FolioContentBlock, type FolioContentBlockProperty, type FolioContentComparison, type FolioContentComparisonError, type FolioContentComparisonEvent, type FolioContentComparisonLimit, FolioContentComparisonLimitError, type FolioContentContainerPathEntry, type FolioContentFormatRange, type FolioContentFormattingChange, type FolioContentIdStability, type FolioContentInlineBooleanProperty, type FolioContentInlineComparisonResult, type FolioContentInlineFormatting, type FolioContentInlineFormattingPatch, FolioContentInlinePresentationProjectionError, type FolioContentLineSpacingRule, type FolioContentParagraphAlignment, type FolioContentParagraphFormattingPatch, type FolioContentParagraphKind, type FolioContentParagraphSpacing, type FolioContentRun, type FolioContentSnapshot, type FolioContentStructuralChange, type FolioContentTableLocation, type FolioContentTextSegment, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationResultBase, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocxCompatibilityHost, type FolioDocxCompatibilityProfile, type FolioRevisionStamp, type FolioWordDiffOptions, type ImageMeta, type ImageRef, type InspectDocxCompatibilityOptions, InvalidCompareDocxOptionsError, InvalidFolioContentComparisonError, InvalidFolioDocumentOperationBatchError, MAX_COMPARE_OPERATIONS, type MarkdownOptions, type MarkdownResult, type PositionalText, type ResolvedAnchor, STELLA_STYLE_SET_NAME, type TemplatePreviewSpan, type TemplatePreviewValue, type TemplatePreviewValues, type TemplateSlashMenuKeyAction, type TemplateSlashMenuState, UnsupportedFolioDocumentOperationVersionError, WORD_DIFF_GRANULARITIES, type WordDiffGranularity, type WordDiffNormalization, type WordDiffOptions, type WordDiffSegment, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildEmbeddedFontFamilyMap, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, compareContent, compareDocx, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, createStellaStyleDocumentPreset, createStellaStyleSet, currentFolioBlockId, deriveBlockId, diffWordSegments, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxCompatibility, isFolioAIContentBlock, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, mergeDocumentContent, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scopeEmbeddedFontFamily, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setEmbeddedFontFamilyMap, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
44
+ export { type AIBarStatus, type AIChatMode, type AICitation, type AICitationRange, type AICitationSource, type AIGenerateInput, type AISuggestion, type AISuggestionApplyMode, type AISuggestionPreset, type AISuggestionSeverity, type AISuggestionStatus, type AcceptAutocompleteResult, type AnonymizationMatch, type AnonymizationTerm, type ApplyFolioDocumentOperationsOptions, type ApplyResult, type AutocompleteSuggestionPluginOptions, type AutocompleteSuggestionState, type AutocompleteSuggestionStatus, type AutocompleteTriggerCheck, type AutocompleteTriggerOptions, type AutocompleteTriggerSkipReason, type BlockRect, COMPARE_REVISION_FORMATS, COMPARE_UNSUPPORTED_REASONS, COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS, type CompareChange, type CompareChangeLocation, type CompareCompatibility, type CompareContentOptions, CompareDocxApplyError, type CompareDocxError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, type CompareDocxOptions, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, type CompareFolioRequirement, type CompareResult, type CompareRevisionFormat, type CompareUnsupportedPart, type CompareUnsupportedReason, type CompareVerification, type CompareVerificationCause, type CompareVerificationFailure, type CompareVerificationInvariant, type CreateEmptyDocumentOptions, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DOCX_CONFORMANCE_CLASSES, DRAWING_SAFETY_CLASSES, type DeriveBlockIdInput, type DirectiveKind, type DirectiveRange, type Document, type DocumentPreset, type DocumentPropertiesOptions, type DocumentStyleCatalog, type DocumentStyleCatalogEntry, type DocumentStyleSet, type DocxCompatibility, type DocxCompatibilityContext, type DocxCompatibilityIssue, type DocxCompatibilityLocation, type DocxCompatibilityPart, type DocxConformanceClass, type DocxDrawingClassification, type DrawingSafetyClass, type EmbeddedFont, type EmbeddedFontParts, type ExtractDocumentStyleSetOptions, FOLIO_CONTENT_COMPARISON_LIMITS, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, type FinalParagraphMarkRevision, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIBlockStructuralBoundary, type FolioAIBlockTableLocation, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyOutcome, type FolioAIEditApplyResult, type FolioAIEditNormalization, type FolioAIEditNormalizationCode, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAIInlineBooleanProperty, type FolioAIInlineFormatting, type FolioAIInlineFormattingPatch, type FolioAIParagraphSpacing, type FolioAISignatureParty, type FolioBlockId, type FolioContentBlock, type FolioContentBlockProperty, type FolioContentComparison, type FolioContentComparisonError, type FolioContentComparisonEvent, type FolioContentComparisonLimit, FolioContentComparisonLimitError, type FolioContentContainerPathEntry, type FolioContentFormatRange, type FolioContentFormattingChange, type FolioContentIdStability, type FolioContentInlineBooleanProperty, type FolioContentInlineComparisonResult, type FolioContentInlineFormatting, type FolioContentInlineFormattingPatch, FolioContentInlinePresentationProjectionError, type FolioContentLineSpacingRule, type FolioContentParagraphAlignment, type FolioContentParagraphFormattingPatch, type FolioContentParagraphKind, type FolioContentParagraphSpacing, type FolioContentRun, type FolioContentSnapshot, type FolioContentStructuralChange, type FolioContentTableLocation, type FolioContentTextSegment, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationResultBase, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocxCompatibilityHost, type FolioDocxCompatibilityProfile, type FolioRevisionStamp, type FolioWordDiffOptions, type ImageMeta, type ImageRef, type InspectDocxCompatibilityOptions, InvalidCompareDocxOptionsError, InvalidFolioContentComparisonError, InvalidFolioDocumentOperationBatchError, MAX_COMPARE_OPERATIONS, type MarkdownOptions, type MarkdownResult, type PositionalText, type ResolvedAnchor, STELLA_STYLE_SET_NAME, type TemplatePreviewHiddenRange, type TemplatePreviewSpan, type TemplatePreviewValue, type TemplatePreviewValues, type TemplateSlashMenuKeyAction, type TemplateSlashMenuState, UnsupportedFolioDocumentOperationVersionError, WORD_DIFF_GRANULARITIES, type WordDiffGranularity, type WordDiffNormalization, type WordDiffOptions, type WordDiffSegment, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildEmbeddedFontFamilyMap, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, compareContent, compareDocx, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, createStellaStyleDocumentPreset, createStellaStyleSet, currentFolioBlockId, deriveBlockId, diffWordSegments, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxCompatibility, isFolioAIContentBlock, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, mergeDocumentContent, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scopeEmbeddedFontFamily, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setEmbeddedFontFamilyMap, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
@@ -2,6 +2,8 @@ import { document_d_exports } from "../types/document.js";
2
2
  //#region src/internal/pageBreakRunSourceDescendantIndex.d.ts
3
3
  type PageBreakRunSourceDescendantIndex = {
4
4
  containsPageBreakRun: (content: readonly document_d_exports.BlockContent[]) => boolean;
5
+ /** The same question about one block, so a caller can ask it per sibling. */
6
+ blockContainsPageBreakRun: (block: document_d_exports.BlockContent) => boolean;
5
7
  paragraphFeatures: (paragraph: document_d_exports.Paragraph) => PageBreakRunSourceParagraphFeatures;
6
8
  };
7
9
  type PageBreakRunSourceParagraphFeatures = {
@@ -105,12 +105,14 @@ const buildPageBreakRunSourceDescendantIndex = (rootContent) => {
105
105
  return flags;
106
106
  }
107
107
  inspectBlocks(rootContent);
108
+ const indexedFlags = (node) => {
109
+ const flags = flagsByObject.get(node);
110
+ if (flags === void 0) panic("Page-break source ownership was queried outside its conversion index");
111
+ return flags;
112
+ };
108
113
  return {
109
- containsPageBreakRun: (content) => {
110
- const flags = flagsByObject.get(content);
111
- if (flags === void 0) panic("Page-break source ownership was queried outside its conversion index");
112
- return hasFlag(flags, SOURCE_FLAGS.containsPageBreak);
113
- },
114
+ containsPageBreakRun: (content) => hasFlag(indexedFlags(content), SOURCE_FLAGS.containsPageBreak),
115
+ blockContainsPageBreakRun: (block) => hasFlag(indexedFlags(block), SOURCE_FLAGS.containsPageBreak),
114
116
  paragraphFeatures: (paragraph) => {
115
117
  const flags = flagsByObject.get(paragraph);
116
118
  if (flags === void 0) panic("Page-break paragraph ownership was queried outside its conversion index");
@@ -1,4 +1,4 @@
1
- import { TemplatePreviewValue } from "../../prosemirror/plugins/templatePreviewValues.js";
1
+ import { TemplatePreviewHiddenRange, TemplatePreviewValue } from "../../prosemirror/plugins/templatePreviewValues.js";
2
2
  import { FlowBlock } from "../../layout-engine/types.js";
3
3
  //#region src/layout-bridge/convert/templatePreviewFlow.d.ts
4
4
  /** One marker→value substitution, in PM doc positions. */
@@ -10,27 +10,35 @@ type TemplatePreviewFlowEntry = {
10
10
  /** The typed value displayed in place of the marker. */
11
11
  value: TemplatePreviewValue;
12
12
  };
13
- type TemplatePreviewFlowOptions = {
13
+ /** What the preview substitutes and what it hides, as the flow stage sees it. */
14
+ type TemplatePreviewFlowState = {
14
15
  entries: readonly TemplatePreviewFlowEntry[];
16
+ /** Conditional spans a `false` condition hides, in PM doc positions. */
17
+ hidden: readonly TemplatePreviewHiddenRange[];
18
+ };
19
+ type TemplatePreviewFlowOptions = TemplatePreviewFlowState & {
15
20
  /** `highlighted` marks substituted runs for the accent-chip CSS. */
16
21
  mode: "highlighted" | "plain";
17
22
  };
18
23
  /**
19
24
  * Replace each entry's marker range with its preview value across the given
20
- * flow blocks (recursing into table cells and text boxes). Returns the input
21
- * array unchanged when there is nothing to substitute.
25
+ * flow blocks (recursing into table cells and text boxes), and drop the blocks
26
+ * a hidden conditional span swallows whole. Returns the input array unchanged
27
+ * when there is nothing to substitute and nothing to hide.
22
28
  */
23
- declare function applyTemplatePreviewToBlocks(blocks: FlowBlock[], { entries, mode }: TemplatePreviewFlowOptions): FlowBlock[];
29
+ declare function applyTemplatePreviewToBlocks(blocks: FlowBlock[], { entries, hidden, mode }: TemplatePreviewFlowOptions): FlowBlock[];
24
30
  /**
25
- * PM range covering every substitution that differs between two preview
26
- * states (changed, added, or removed entries), or `null` when the
27
- * substituted flow content is identical. Feeds the layout pipeline's
31
+ * PM range covering every substitution and every hidden span that differs
32
+ * between two preview states (changed, added, or removed), or `null` when the
33
+ * flow content both produce is identical. Feeds the layout pipeline's
28
34
  * dirty-range invalidation so typing a value re-measures only the blocks
29
- * hosting the affected markers.
35
+ * hosting the affected markers. A hidden span that appears or disappears also
36
+ * changes the block count, which sends the measure path down its full-remeasure
37
+ * branch regardless of how wide this range is.
30
38
  */
31
- declare function templatePreviewDirtyRange(previous: readonly TemplatePreviewFlowEntry[], next: readonly TemplatePreviewFlowEntry[]): {
39
+ declare function templatePreviewDirtyRange(previous: TemplatePreviewFlowState, next: TemplatePreviewFlowState): {
32
40
  from: number;
33
41
  to: number;
34
42
  } | null;
35
43
  //#endregion
36
- export { TemplatePreviewFlowEntry, TemplatePreviewFlowOptions, applyTemplatePreviewToBlocks, templatePreviewDirtyRange };
44
+ export { TemplatePreviewFlowEntry, TemplatePreviewFlowOptions, TemplatePreviewFlowState, applyTemplatePreviewToBlocks, templatePreviewDirtyRange };
@@ -1,16 +1,41 @@
1
- import { templatePreviewValueFingerprint } from "../../prosemirror/plugins/templatePreviewValues.js";
1
+ import { templatePreviewHidesWholeBlock, templatePreviewValueFingerprint } from "../../prosemirror/plugins/templatePreviewValues.js";
2
2
  //#region src/layout-bridge/convert/templatePreviewFlow.ts
3
3
  /**
4
+ * A top-level block a hidden span swallows whole, which therefore leaves the
5
+ * flow. A block the span only partly covers stays: its text is authored content
6
+ * outside the conditional, and dropping the block would take that text with it.
7
+ *
8
+ * A block carrying no PM positions stays too. That is every section break,
9
+ * which is the safe answer rather than an accident: a section break sets the
10
+ * page geometry that follows it, so hiding text must not take it out of the
11
+ * flow. `SectionBreakBlock` is the one `FlowBlock` variant without the fields,
12
+ * hence the `in` test, matching `findDirtyBlockIndexes`.
13
+ */
14
+ const isHiddenWholeBlock = (block, hidden) => {
15
+ const pmStart = "pmStart" in block ? block.pmStart : void 0;
16
+ const pmEnd = "pmEnd" in block ? block.pmEnd : void 0;
17
+ if (pmStart === void 0 || pmEnd === void 0) return false;
18
+ return hidden.some((range) => templatePreviewHidesWholeBlock(range, {
19
+ pmStart,
20
+ pmEnd
21
+ }));
22
+ };
23
+ /**
4
24
  * Replace each entry's marker range with its preview value across the given
5
- * flow blocks (recursing into table cells and text boxes). Returns the input
6
- * array unchanged when there is nothing to substitute.
25
+ * flow blocks (recursing into table cells and text boxes), and drop the blocks
26
+ * a hidden conditional span swallows whole. Returns the input array unchanged
27
+ * when there is nothing to substitute and nothing to hide.
7
28
  */
8
- function applyTemplatePreviewToBlocks(blocks, { entries, mode }) {
9
- if (entries.length === 0) return blocks;
29
+ function applyTemplatePreviewToBlocks(blocks, { entries, hidden, mode }) {
30
+ if (entries.length === 0 && hidden.length === 0) return blocks;
10
31
  const sorted = [...entries].sort((a, b) => a.from - b.from);
11
32
  let changed = false;
12
33
  const next = [];
13
34
  for (const block of blocks) {
35
+ if (isHiddenWholeBlock(block, hidden)) {
36
+ changed = true;
37
+ continue;
38
+ }
14
39
  const transformed = transformBlock(block, sorted, mode);
15
40
  changed ||= transformed !== block;
16
41
  next.push(transformed);
@@ -117,60 +142,100 @@ function sliceTextRun(run, base, from, to) {
117
142
  pmEnd: to
118
143
  };
119
144
  }
145
+ /** Every newline form a host value may carry; `\r\n` counts once. */
146
+ const VALUE_LINE_BREAK_RE = /\r\n|[\n\r]/u;
147
+ /**
148
+ * Push one span of value text, breaking its lines the way the editor does: each
149
+ * newline becomes a `lineBreak` run, which is the `w:br` the measurer starts a
150
+ * new line on. Consecutive newlines emit consecutive breaks, so a blank line
151
+ * stays a blank line; an empty segment contributes no text run of its own.
152
+ *
153
+ * A rich value's spans are one text stream, so a `\r\n` the host split across a
154
+ * formatting boundary is still one break: `pendingCr` carries a `\r` that ended
155
+ * the previous span, and the returned flag carries this span's. Without it the
156
+ * halves would count as two breaks and the value would gain a blank line.
157
+ */
158
+ function pushValueSpan(out, valueRun, pendingCr) {
159
+ const text = pendingCr && valueRun.text.startsWith("\n") ? valueRun.text.slice(1) : valueRun.text;
160
+ const segments = text.split(VALUE_LINE_BREAK_RE);
161
+ for (const [index, segment] of segments.entries()) {
162
+ if (index > 0) out.push({
163
+ kind: "lineBreak",
164
+ pmStart: valueRun.pmStart,
165
+ pmEnd: valueRun.pmEnd
166
+ });
167
+ if (segment !== "") out.push({
168
+ ...valueRun,
169
+ text: segment
170
+ });
171
+ }
172
+ return text.endsWith("\r");
173
+ }
120
174
  /**
121
175
  * The value run(s) replacing a marker. A plain value is one run carrying the
122
176
  * host run's formatting; a rich value emits one run per span, each layering
123
177
  * its own bold/italic over the host formatting (host bold stays bold, a
124
- * span's flags OR in). Every run keeps the marker's full PM range so
125
- * click-to-position keeps resolving into the marker.
178
+ * span's flags OR in). A newline in either splits its span around a line
179
+ * break run. Every run keeps the marker's full PM range so click-to-position
180
+ * keeps resolving into the marker.
126
181
  */
127
182
  function buildValueRuns(host, entry, mode) {
128
- if (typeof entry.value === "string") return [{
183
+ const runs = [];
184
+ const base = {
129
185
  ...host,
130
- text: entry.value,
131
186
  pmStart: entry.from,
132
187
  pmEnd: entry.to,
133
188
  templatePreview: mode
134
- }];
135
- return entry.value.runs.map((span) => {
189
+ };
190
+ if (typeof entry.value === "string") {
191
+ pushValueSpan(runs, {
192
+ ...base,
193
+ text: entry.value
194
+ }, false);
195
+ return runs;
196
+ }
197
+ let pendingCr = false;
198
+ for (const span of entry.value.runs) {
136
199
  const valueRun = {
137
- ...host,
138
- text: span.text,
139
- pmStart: entry.from,
140
- pmEnd: entry.to,
141
- templatePreview: mode
200
+ ...base,
201
+ text: span.text
142
202
  };
143
203
  if (span.bold === true) valueRun.bold = true;
144
204
  if (span.italic === true) valueRun.italic = true;
145
- return valueRun;
146
- });
205
+ pendingCr = pushValueSpan(runs, valueRun, pendingCr);
206
+ }
207
+ return runs;
147
208
  }
148
209
  const entryKey = (entry) => `${entry.from}:${entry.to}:${templatePreviewValueFingerprint(entry.value)}`;
210
+ const hiddenKey = (range) => `${range.from}:${range.to}:${range.expr}`;
211
+ /** Widen `bounds` over every range whose key is absent from `known`. */
212
+ const growOverUnknown = (ranges, key, known, bounds) => {
213
+ for (const range of ranges) {
214
+ if (known.has(key(range))) continue;
215
+ bounds.from = Math.min(bounds.from, range.from);
216
+ bounds.to = Math.max(bounds.to, range.to);
217
+ }
218
+ };
149
219
  /**
150
- * PM range covering every substitution that differs between two preview
151
- * states (changed, added, or removed entries), or `null` when the
152
- * substituted flow content is identical. Feeds the layout pipeline's
220
+ * PM range covering every substitution and every hidden span that differs
221
+ * between two preview states (changed, added, or removed), or `null` when the
222
+ * flow content both produce is identical. Feeds the layout pipeline's
153
223
  * dirty-range invalidation so typing a value re-measures only the blocks
154
- * hosting the affected markers.
224
+ * hosting the affected markers. A hidden span that appears or disappears also
225
+ * changes the block count, which sends the measure path down its full-remeasure
226
+ * branch regardless of how wide this range is.
155
227
  */
156
228
  function templatePreviewDirtyRange(previous, next) {
157
- const previousKeys = new Set(previous.map(entryKey));
158
- const nextKeys = new Set(next.map(entryKey));
159
- let from = Number.POSITIVE_INFINITY;
160
- let to = Number.NEGATIVE_INFINITY;
161
- for (const entry of previous) if (!nextKeys.has(entryKey(entry))) {
162
- from = Math.min(from, entry.from);
163
- to = Math.max(to, entry.to);
164
- }
165
- for (const entry of next) if (!previousKeys.has(entryKey(entry))) {
166
- from = Math.min(from, entry.from);
167
- to = Math.max(to, entry.to);
168
- }
169
- if (from === Number.POSITIVE_INFINITY) return null;
170
- return {
171
- from,
172
- to
229
+ const bounds = {
230
+ from: Number.POSITIVE_INFINITY,
231
+ to: Number.NEGATIVE_INFINITY
173
232
  };
233
+ growOverUnknown(previous.entries, entryKey, new Set(next.entries.map(entryKey)), bounds);
234
+ growOverUnknown(next.entries, entryKey, new Set(previous.entries.map(entryKey)), bounds);
235
+ growOverUnknown(previous.hidden, hiddenKey, new Set(next.hidden.map(hiddenKey)), bounds);
236
+ growOverUnknown(next.hidden, hiddenKey, new Set(previous.hidden.map(hiddenKey)), bounds);
237
+ if (bounds.from === Number.POSITIVE_INFINITY) return null;
238
+ return bounds;
174
239
  }
175
240
  //#endregion
176
241
  export { applyTemplatePreviewToBlocks, templatePreviewDirtyRange };
@@ -1175,6 +1175,15 @@ const runIsZeroWidthBoundaryMarker = (run) => {
1175
1175
  default: return run;
1176
1176
  }
1177
1177
  };
1178
+ /** Every page-break run under a node, nested tables and text boxes included. */
1179
+ const countPageBreakRuns = (node) => {
1180
+ let count = node.type.name === "pageBreakRun" ? 1 : 0;
1181
+ node.descendants((descendant) => {
1182
+ if (descendant.type.name === "pageBreakRun") count += 1;
1183
+ return true;
1184
+ });
1185
+ return count;
1186
+ };
1178
1187
  /** Decide leading-break eligibility from the exact projected runs consumed by layout. */
1179
1188
  const hasSingleLeadingProjectedPageBreak = (runs, pageBreaks) => {
1180
1189
  if (pageBreaks.length !== 1) return false;
@@ -1348,12 +1357,12 @@ function convertTableCell(node, startPos, options, tableCellMargins) {
1348
1357
  const blocks = [];
1349
1358
  let offset = startPos + 1;
1350
1359
  const authoredPageBreakPosition = options.firstPageBreakRunPosition(node);
1351
- const singleParagraph = node.childCount === 1 && node.firstChild?.type.name === "paragraph" ? node.firstChild : void 0;
1352
- if (authoredPageBreakPosition !== void 0 && singleParagraph === void 0) panic(`An explicit page-break run at ${String(authoredPageBreakPosition)} cannot be projected inside a table cell`);
1360
+ const leadingParagraph = node.firstChild?.type.name === "paragraph" ? node.firstChild : void 0;
1361
+ if (authoredPageBreakPosition !== void 0 && (leadingParagraph === void 0 || countPageBreakRuns(node) !== 1 || options.firstPageBreakRunPosition(leadingParagraph) !== authoredPageBreakPosition)) panic(`An explicit page-break run at ${String(authoredPageBreakPosition)} cannot be projected inside a table cell`);
1353
1362
  const pageBreaks = [];
1354
1363
  node.forEach((child) => {
1355
1364
  if (child.type.name === "paragraph") {
1356
- const block = convertParagraph(child, offset, options, child === singleParagraph ? pageBreaks : void 0);
1365
+ const block = convertParagraph(child, offset, options, child === leadingParagraph ? pageBreaks : void 0);
1357
1366
  blocks.push(block);
1358
1367
  } else if (child.type.name === "table") blocks.push(convertTable(child, offset, options));
1359
1368
  else if (child.type.name === "textBox") blocks.push(convertTextBoxNode(child, offset, options));
@@ -5,6 +5,17 @@ const UNSUPPORTED_PAGE_BREAK_RUN_ANCESTORS = /* @__PURE__ */ new Set([
5
5
  "tableHeader",
6
6
  "textBox"
7
7
  ]);
8
+ const hasTextBoxAnchorAtOrAfter = (paragraph, contentStart, position) => {
9
+ let found = false;
10
+ paragraph.descendants((descendant, offset) => {
11
+ if (descendant.type.name === "textBoxAnchor" && contentStart + offset >= position) {
12
+ found = true;
13
+ return false;
14
+ }
15
+ return !found;
16
+ });
17
+ return found;
18
+ };
8
19
  /**
9
20
  * Insert a page-break run at the current text cursor.
10
21
  *
@@ -18,7 +29,7 @@ const insertPageBreak = (state, dispatch) => {
18
29
  const { $from } = state.selection;
19
30
  if (!pageBreakRunType || !state.selection.empty || !$from.parent.isTextblock) return false;
20
31
  for (let depth = $from.depth; depth >= 0; depth--) if (UNSUPPORTED_PAGE_BREAK_RUN_ANCESTORS.has($from.node(depth).type.name)) return false;
21
- if ($from.parent.type.name !== "paragraph" || pageBreakRunParagraphProjectionDisposition($from.parent).status === "unsupported") return false;
32
+ if ($from.parent.type.name !== "paragraph" || pageBreakRunParagraphProjectionDisposition($from.parent).status === "unsupported" || hasTextBoxAnchorAtOrAfter($from.parent, $from.start(), $from.pos)) return false;
22
33
  if (dispatch) {
23
34
  const pageBreak = pageBreakRunType.create();
24
35
  dispatch(state.tr.replaceSelectionWith(pageBreak).scrollIntoView());
@@ -1,4 +1,5 @@
1
1
  import { EDITED_PREVIEW_FINGERPRINT, imageRawXmlFingerprint } from "../../docx/imageRawXml.js";
2
+ import { isNumberingReference } from "../../docx/numberingReference.js";
2
3
  import { PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR, ParagraphPropertySourceValidationError, copyDocumentParagraphPropertySourceContract, copyDocumentParagraphPropertySources, copyParagraphPropertyCapture, copyParagraphPropertySource, decodeTableCellParagraphSourcePayload, getDocumentParagraphPropertySourceContract, getParagraphPropertySource, getParagraphPropertySourceCandidate, getParagraphPropertySourceToken, getParagraphPropertySourceTransferId, getProseDocumentParagraphPropertySourceContract, getProseParagraphPropertySourceToken, isParagraphPropertySourceToken, linkParagraphPropertySourceCandidate, paragraphPropertySourceBelongsToDocument, paragraphPropertySourceTokenMatchesContract, recreateProseNodeWithParagraphPropertySource, restoreTableCellsWithParagraphPropertySources, visitDocumentStoryParagraphs, visitTableCellParagraphPropertySourceBindings } from "../../docx/paragraphPropertySource.js";
3
4
  import { visitDocxParagraphs } from "../../docx/paragraphTraversal.js";
4
5
  import { ShapeOutlineStyleSchema, narrowEnum } from "../../docx/parserEnums.js";
@@ -689,7 +690,7 @@ function removeTextBoxAnchorFromBlocks(blocks, marker) {
689
690
  */
690
691
  function listRenderingFromAttrs(attrs) {
691
692
  const numId = attrs.numPr?.numId;
692
- if (numId === void 0 || numId === 0) return;
693
+ if (!isNumberingReference(numId)) return;
693
694
  if (!(attrs.listMarker != null || attrs.listIsBullet || attrs.listNumFmt != null)) return;
694
695
  return listRenderingFromAttrs$1({
695
696
  attrs,