@stll/folio-core 0.28.1 → 0.30.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 (125) hide show
  1. package/dist/controller/folioEditor.d.ts +28 -1
  2. package/dist/controller/folioEditor.js +37 -26
  3. package/dist/controller/fontReadiness.js +28 -24
  4. package/dist/controller/hiddenEditorManager.d.ts +2 -7
  5. package/dist/controller/layoutPipeline.js +33 -3
  6. package/dist/controller/noteEditorManager.d.ts +3 -6
  7. package/dist/docx/metafileSvg.d.ts +12 -0
  8. package/dist/docx/metafileSvg.js +462 -0
  9. package/dist/docx/paragraphTextBoxEnrichment.js +55 -4
  10. package/dist/docx/parser.d.ts +2 -2
  11. package/dist/docx/parser.js +26 -0
  12. package/dist/docx/runParser.js +2 -1
  13. package/dist/docx/serializer/runSerializer.js +14 -2
  14. package/dist/docx/styleParser.js +2 -1
  15. package/dist/docx/textBoxParser.js +14 -0
  16. package/dist/docx/vmlImageParser.js +72 -152
  17. package/dist/docx/vmlPreview.d.ts +31 -0
  18. package/dist/docx/vmlPreview.js +535 -0
  19. package/dist/docx/wrapTypes.d.ts +2 -36
  20. package/dist/docx/wrapTypes.js +1 -39
  21. package/dist/fonts/fontAlternates.d.ts +8 -0
  22. package/dist/fonts/fontAlternates.js +16 -0
  23. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  24. package/dist/layout-bridge/convert/footnoteLayout.js +34 -3
  25. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  26. package/dist/layout-bridge/convert/headerFooterLayout.js +5 -0
  27. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -2
  28. package/dist/layout-bridge/convert/toFlowBlocks.js +136 -120
  29. package/dist/layout-bridge/dom/noteStoryDom.d.ts +1 -1
  30. package/dist/layout-bridge/engine/selectionRects.js +3 -1
  31. package/dist/layout-engine/index.js +65 -32
  32. package/dist/layout-engine/justifiedLineFit.d.ts +7 -0
  33. package/dist/layout-engine/justifiedLineFit.js +6 -0
  34. package/dist/layout-engine/keep-together.js +15 -10
  35. package/dist/layout-engine/measure/complexScriptFormatting.d.ts +6 -1
  36. package/dist/layout-engine/measure/complexScriptFormatting.js +11 -2
  37. package/dist/layout-engine/measure/floatingTablePosition.d.ts +16 -1
  38. package/dist/layout-engine/measure/floatingTablePosition.js +29 -6
  39. package/dist/layout-engine/measure/lineBreakProvider.js +67 -8
  40. package/dist/layout-engine/measure/listMarkerWidth.d.ts +1 -0
  41. package/dist/layout-engine/measure/listMarkerWidth.js +23 -10
  42. package/dist/layout-engine/measure/measureBlocks.d.ts +15 -0
  43. package/dist/layout-engine/measure/measureBlocks.js +197 -30
  44. package/dist/layout-engine/measure/measureContainer.js +20 -24
  45. package/dist/layout-engine/measure/measureHelpers.d.ts +1 -1
  46. package/dist/layout-engine/measure/measureHelpers.js +14 -8
  47. package/dist/layout-engine/measure/measureParagraph.js +111 -37
  48. package/dist/layout-engine/measure/measureTypes.d.ts +3 -0
  49. package/dist/layout-engine/measure/tabCalculator.d.ts +123 -0
  50. package/dist/layout-engine/measure/tabCalculator.js +144 -0
  51. package/dist/layout-engine/measure/tableCellFlow.d.ts +21 -1
  52. package/dist/layout-engine/measure/tableCellFlow.js +45 -8
  53. package/dist/layout-engine/measure/tableInlinePlacement.d.ts +10 -2
  54. package/dist/layout-engine/measure/tableInlinePlacement.js +17 -15
  55. package/dist/layout-engine/paginator.d.ts +26 -2
  56. package/dist/layout-engine/paginator.js +76 -14
  57. package/dist/layout-engine/paragraphSpacing.d.ts +3 -1
  58. package/dist/layout-engine/paragraphSpacing.js +4 -3
  59. package/dist/layout-engine/renderedBreakReconciliation.d.ts +14 -3
  60. package/dist/layout-engine/renderedBreakReconciliation.js +45 -14
  61. package/dist/layout-engine/section-breaks.d.ts +3 -1
  62. package/dist/layout-engine/section-breaks.js +6 -2
  63. package/dist/layout-engine/types.d.ts +18 -1
  64. package/dist/layout-engine/types.js +1 -1
  65. package/dist/layout-painter/imageLayout.d.ts +1 -1
  66. package/dist/layout-painter/renderPage.js +6 -1
  67. package/dist/layout-painter/renderParagraph.js +169 -82
  68. package/dist/layout-painter/renderTable.d.ts +1 -1
  69. package/dist/layout-painter/renderTable.js +18 -7
  70. package/dist/layout-painter/renderTextBox.d.ts +1 -1
  71. package/dist/layout-painter/renderTextBox.js +6 -1
  72. package/dist/markdown/renderParagraph.js +7 -2
  73. package/dist/model.d.ts +3 -3
  74. package/dist/model.js +2 -2
  75. package/dist/paged-layout/sectionBlockWidths.d.ts +14 -0
  76. package/dist/paged-layout/sectionBlockWidths.js +103 -14
  77. package/dist/prosemirror/attrs/index.js +14 -2
  78. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +8 -0
  79. package/dist/prosemirror/bookmarkBoundaryAttrs.js +66 -0
  80. package/dist/prosemirror/conversion/fromProseDoc.js +176 -54
  81. package/dist/prosemirror/conversion/toProseDoc.js +179 -64
  82. package/dist/prosemirror/extensions/StarterKit.js +11 -3
  83. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +8 -4
  84. package/dist/prosemirror/extensions/features/PasteCleanupExtension.d.ts +4 -1
  85. package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +9 -5
  86. package/dist/prosemirror/extensions/features/pasteCleanup.d.ts +10 -23
  87. package/dist/prosemirror/extensions/features/pasteCleanup.js +77 -22
  88. package/dist/prosemirror/extensions/marks/CharacterSpacingExtension.js +9 -5
  89. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +9 -0
  90. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +67 -0
  91. package/dist/prosemirror/extensions/nodes/FieldExtension.d.ts +10 -4
  92. package/dist/prosemirror/extensions/nodes/FieldExtension.js +77 -59
  93. package/dist/prosemirror/extensions/nodes/TableExtension.js +2 -0
  94. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +4 -1
  95. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +4 -3
  96. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +6 -2
  97. package/dist/prosemirror/listMarker.d.ts +58 -0
  98. package/dist/prosemirror/listMarker.js +185 -0
  99. package/dist/prosemirror/numberedRefFields.d.ts +24 -0
  100. package/dist/prosemirror/numberedRefFields.js +276 -0
  101. package/dist/prosemirror/paraText.js +56 -17
  102. package/dist/prosemirror/plugins/anonymizationDecorations.js +1 -1
  103. package/dist/prosemirror/plugins/pmTextScan.d.ts +3 -1
  104. package/dist/prosemirror/plugins/pmTextScan.js +28 -7
  105. package/dist/prosemirror/plugins/templateDirectives.js +2 -2
  106. package/dist/prosemirror/schema/index.d.ts +2 -2
  107. package/dist/prosemirror/schema/nodes.d.ts +17 -1
  108. package/dist/prosemirror/utils/tabCalculator.d.ts +2 -123
  109. package/dist/prosemirror/utils/tabCalculator.js +1 -140
  110. package/dist/prosemirror/validation.js +93 -0
  111. package/dist/render-dom/RemoteSelectionOverlay.d.ts +4 -4
  112. package/dist/types/documentEnumValues.d.ts +4 -1
  113. package/dist/types/documentEnumValues.js +12 -1
  114. package/dist/types/editor-story.d.ts +8 -0
  115. package/dist/types/editor-story.js +0 -0
  116. package/dist/types/remote-selection.d.ts +11 -0
  117. package/dist/types/remote-selection.js +0 -0
  118. package/dist/types/wrap.d.ts +36 -0
  119. package/dist/types/wrap.js +40 -0
  120. package/dist/utils/fontResolver.d.ts +1 -1
  121. package/dist/utils/fontResolver.js +42 -2
  122. package/dist/utils/formatToStyle.js +4 -2
  123. package/dist/utils/horizontalScale.d.ts +28 -0
  124. package/dist/utils/horizontalScale.js +42 -0
  125. package/package.json +2 -2
@@ -0,0 +1,185 @@
1
+ import { convertBulletToUnicode } from "../docx/bulletMarkers.js";
2
+ import { formatOoxmlCounter } from "../docx/ooxmlCounterFormatter.js";
3
+ //#region src/prosemirror/listMarker.ts
4
+ const MAX_LIST_LEVEL = 8;
5
+ function createListCounterState() {
6
+ return {
7
+ counters: /* @__PURE__ */ new Map(),
8
+ abstractCounters: /* @__PURE__ */ new Map(),
9
+ seenLevels: /* @__PURE__ */ new Set()
10
+ };
11
+ }
12
+ function cloneListCounterState(state) {
13
+ const counters = /* @__PURE__ */ new Map();
14
+ for (const [numId, values] of state.counters) counters.set(numId, [...values]);
15
+ const abstractCounters = /* @__PURE__ */ new Map();
16
+ for (const [abstractNumId, values] of state.abstractCounters) abstractCounters.set(abstractNumId, [...values]);
17
+ return {
18
+ counters,
19
+ abstractCounters,
20
+ seenLevels: new Set(state.seenLevels),
21
+ ...state.lastAdvancedNumId !== void 0 ? { lastAdvancedNumId: state.lastAdvancedNumId } : {}
22
+ };
23
+ }
24
+ const isListNumPr = (value) => value !== void 0 && value !== null;
25
+ const sameListNumPr = (left, right) => left.numId === right.numId && left.ilvl === right.ilvl;
26
+ function previousListAttrs(attrs) {
27
+ const previous = (attrs._propertyChanges?.find(({ previousFormatting }) => {
28
+ const previousNumPr = previousFormatting?.numPr;
29
+ return isListNumPr(previousNumPr) && (!attrs.numPr || !sameListNumPr(previousNumPr, attrs.numPr));
30
+ }))?.previousFormatting;
31
+ if (!previous || !isListNumPr(previous.numPr)) return null;
32
+ return {
33
+ numPr: previous.numPr,
34
+ ...previous.listIsBullet !== void 0 ? { listIsBullet: previous.listIsBullet } : {},
35
+ ...previous.listIsLegal !== void 0 ? { listIsLegal: previous.listIsLegal } : {},
36
+ ...previous.listNumFmt !== void 0 ? { listNumFmt: previous.listNumFmt } : {},
37
+ ...previous.listMarker !== void 0 ? { listMarker: previous.listMarker } : {},
38
+ ...previous.listMarkerHidden !== void 0 ? { listMarkerHidden: previous.listMarkerHidden } : {},
39
+ ...previous.listLevelNumFmts !== void 0 ? { listLevelNumFmts: previous.listLevelNumFmts } : {},
40
+ ...previous.listLevelStarts !== void 0 ? { listLevelStarts: previous.listLevelStarts } : {},
41
+ ...previous.listAbstractNumId !== void 0 ? { listAbstractNumId: previous.listAbstractNumId } : {},
42
+ ...previous.listStartOverride !== void 0 ? { listStartOverride: previous.listStartOverride } : {}
43
+ };
44
+ }
45
+ /**
46
+ * Advance the counter stream that paints this paragraph's marker. Normal
47
+ * paragraphs advance both final and original streams; inserted/deleted list
48
+ * paragraphs advance only the document view in which they exist.
49
+ */
50
+ function advanceVisibleListMarker(attrs, streams) {
51
+ const advances = [];
52
+ const advance = (counterAttrs, stream) => {
53
+ const counterState = streams[stream];
54
+ const advanced = {
55
+ marker: advanceListMarker(counterAttrs, counterState),
56
+ counterAttrs,
57
+ counterState,
58
+ stream
59
+ };
60
+ advances.push(advanced);
61
+ return advanced;
62
+ };
63
+ const previous = previousListAttrs(attrs);
64
+ if (!attrs.numPr) return {
65
+ ...advance(previous ?? attrs, previous ? "original" : "final"),
66
+ advances
67
+ };
68
+ if (attrs.pPrMark?.kind === "del") return {
69
+ ...advance(attrs, "original"),
70
+ advances
71
+ };
72
+ const numberingWasAdded = attrs._propertyChanges?.some(({ previousFormatting }) => previousFormatting && Object.hasOwn(previousFormatting, "numPr") && previousFormatting.numPr == null);
73
+ const numberingChanged = previous?.numPr !== void 0 && !sameListNumPr(previous.numPr, attrs.numPr);
74
+ const visible = advance(attrs, "final");
75
+ if (attrs.pPrMark?.kind !== "ins" && !numberingWasAdded && !numberingChanged) advance(attrs, "original");
76
+ else if (numberingChanged && previous) advance(previous, "original");
77
+ return {
78
+ ...visible,
79
+ advances
80
+ };
81
+ }
82
+ function formatCounter(value, format) {
83
+ return formatOoxmlCounter(value, format);
84
+ }
85
+ function resolveListTemplate(options) {
86
+ return resolveListTemplateWithComponents(options).value;
87
+ }
88
+ function resolveListTemplateWithComponents({ template, counters, levelFormats, forceDecimal = false }) {
89
+ const components = [];
90
+ let value = "";
91
+ let sourceEnd = 0;
92
+ for (const match of template.matchAll(/%(?<digit>\d)(?<punct>[.):\]])?/gu)) {
93
+ const sourceStart = match.index;
94
+ const relativeStart = value.length;
95
+ value += template.slice(sourceEnd, sourceStart);
96
+ sourceEnd = sourceStart + match[0].length;
97
+ const index = Number.parseInt(match.groups?.["digit"] ?? "", 10) - 1;
98
+ if (index < 0) continue;
99
+ const counter = counters[index];
100
+ if (counter === void 0 || Number.isNaN(counter)) continue;
101
+ const formatted = formatCounter(counter, forceDecimal ? "decimal" : levelFormats?.[index]);
102
+ if (!formatted) continue;
103
+ const start = value.length;
104
+ value += formatted;
105
+ components.push({
106
+ level: index,
107
+ relativeStart,
108
+ start,
109
+ end: value.length
110
+ });
111
+ value += match.groups?.["punct"] ?? "";
112
+ }
113
+ value += template.slice(sourceEnd);
114
+ return {
115
+ value,
116
+ components
117
+ };
118
+ }
119
+ function getLastListCounters(state) {
120
+ return state.lastAdvancedNumId === void 0 ? void 0 : state.counters.get(state.lastAdvancedNumId);
121
+ }
122
+ function formatNumberedMarker(counters, level) {
123
+ const parts = [];
124
+ for (let index = 0; index <= level; index += 1) {
125
+ const value = counters[index] ?? 0;
126
+ if (!Number.isFinite(value) || value <= 0) break;
127
+ parts.push(value);
128
+ }
129
+ return parts.length === 0 ? "1." : `${parts.join(".")}.`;
130
+ }
131
+ function advanceListMarker(attrs, state) {
132
+ const level = attrs.numPr?.ilvl ?? 0;
133
+ if (!Number.isInteger(level) || level < 0 || level > 8) return null;
134
+ const numId = attrs.numPr?.numId;
135
+ if (numId === void 0 || numId === 0) {
136
+ if (attrs.listMarker?.includes("%") && !attrs.listIsBullet) {
137
+ const counters = getLastListCounters(state);
138
+ if (counters) return resolveListTemplate({
139
+ template: attrs.listMarker,
140
+ counters,
141
+ levelFormats: attrs.listLevelNumFmts,
142
+ forceDecimal: attrs.listIsLegal
143
+ });
144
+ }
145
+ return null;
146
+ }
147
+ if (attrs.listIsBullet) return convertBulletToUnicode(attrs.listMarker ?? "");
148
+ const counters = state.counters.get(numId) ?? Array.from({ length: 9 }, () => NaN);
149
+ const abstractNumId = attrs.listAbstractNumId;
150
+ if (level > 0) {
151
+ const latestAbstractCounters = abstractNumId === void 0 ? void 0 : state.abstractCounters.get(abstractNumId);
152
+ if (counters.slice(0, level).every((counter) => !Number.isFinite(counter))) for (let index = 0; index < level; index += 1) {
153
+ const latestCounter = latestAbstractCounters?.[index];
154
+ counters[index] = latestCounter !== void 0 && Number.isFinite(latestCounter) ? latestCounter : attrs.listLevelStarts?.[index] ?? 1;
155
+ }
156
+ }
157
+ const seenKey = `${numId}:${level}`;
158
+ if (!state.seenLevels.has(seenKey)) {
159
+ state.seenLevels.add(seenKey);
160
+ if (attrs.listStartOverride != null) counters[level] = attrs.listStartOverride - 1;
161
+ }
162
+ if (!Number.isFinite(counters[level])) counters[level] = (attrs.listLevelStarts?.[level] ?? 1) - 1;
163
+ counters[level] = (counters[level] ?? 0) + 1;
164
+ for (let index = level + 1; index < counters.length; index += 1) counters[index] = NaN;
165
+ const childAdvances = attrs.listImplicitChildLevelAdvances ?? 0;
166
+ if (childAdvances > 0 && level + 1 < counters.length) {
167
+ const childCounter = counters[level + 1];
168
+ counters[level + 1] = (childCounter === void 0 || !Number.isFinite(childCounter) ? 0 : childCounter) + childAdvances;
169
+ }
170
+ state.counters.set(numId, counters);
171
+ state.lastAdvancedNumId = numId;
172
+ if (abstractNumId !== void 0) state.abstractCounters.set(abstractNumId, [...counters]);
173
+ const levelFormats = attrs.listLevelNumFmts ?? (attrs.listNumFmt ? [attrs.listNumFmt] : void 0);
174
+ if (attrs.listMarker?.includes("%")) return resolveListTemplate({
175
+ template: attrs.listMarker,
176
+ counters,
177
+ levelFormats,
178
+ forceDecimal: attrs.listIsLegal
179
+ });
180
+ if (attrs.listMarker) return attrs.listMarker;
181
+ if ((levelFormats?.[level] ?? attrs.listNumFmt) === "none" || attrs.listMarker === "") return null;
182
+ return formatNumberedMarker(counters, level);
183
+ }
184
+ //#endregion
185
+ export { MAX_LIST_LEVEL, advanceListMarker, advanceVisibleListMarker, cloneListCounterState, createListCounterState, formatCounter, resolveListTemplate, resolveListTemplateWithComponents };
@@ -0,0 +1,24 @@
1
+ import { ListCounterState } from "./listMarker.js";
2
+ import { Node } from "prosemirror-model";
3
+ //#region src/prosemirror/numberedRefFields.d.ts
4
+ type NumberSwitch = "n" | "r" | "w";
5
+ type NumberedRefSpec = {
6
+ bookmark: string;
7
+ numberSwitch: NumberSwitch;
8
+ };
9
+ type NumberedRefResolutionOptions = {
10
+ /** Counter snapshot at the start of this body story. The resolver consumes it in place. */
11
+ listCounterState?: ListCounterState;
12
+ /** Counter snapshot for tracked deletions in the original document view. */
13
+ originalListCounterState?: ListCounterState;
14
+ };
15
+ declare function parseNumberedRefInstruction(instruction: string): NumberedRefSpec | null;
16
+ /** Stamp only proven numbered REF fields with clone-safe calibration state. */
17
+ declare function stampNumberedRefFieldBaselines(doc: Node): Node;
18
+ /**
19
+ * Resolve bounded, numbered REF fields against bookmarks in the ProseMirror body story.
20
+ * Unsupported or uncalibrated fields are absent from the returned map and retain their cache.
21
+ */
22
+ declare function resolveNumberedRefFields(doc: Node, options?: NumberedRefResolutionOptions): ReadonlyMap<Node, string>;
23
+ //#endregion
24
+ export { NumberedRefResolutionOptions, parseNumberedRefInstruction, resolveNumberedRefFields, stampNumberedRefFieldBaselines };
@@ -0,0 +1,276 @@
1
+ import { expectFieldAttrs, expectParagraphAttrs } from "./attrs/index.js";
2
+ import { expectBookmarkBoundaryAttrs } from "./bookmarkBoundaryAttrs.js";
3
+ import { advanceVisibleListMarker, createListCounterState, resolveListTemplateWithComponents } from "./listMarker.js";
4
+ import { Fragment } from "prosemirror-model";
5
+ //#region src/prosemirror/numberedRefFields.ts
6
+ const MAX_FIELD_INSTRUCTION_CHARS = 2048;
7
+ const MAX_BOOKMARK_NAME_CHARS = 256;
8
+ const MAX_NUMBERED_REF_FIELDS = 1024;
9
+ const MAX_BOOKMARK_TARGETS = 2048;
10
+ function tokenizeInstruction(instruction) {
11
+ const tokens = [];
12
+ let index = 0;
13
+ while (index < instruction.length) {
14
+ while (/\s/u.test(instruction[index] ?? "")) index += 1;
15
+ if (index >= instruction.length) break;
16
+ if (instruction[index] === "\"") {
17
+ const close = instruction.indexOf("\"", index + 1);
18
+ if (close === -1) return null;
19
+ tokens.push(instruction.slice(index + 1, close));
20
+ index = close + 1;
21
+ continue;
22
+ }
23
+ let end = index;
24
+ while (end < instruction.length && !/\s/u.test(instruction[end] ?? "")) end += 1;
25
+ tokens.push(instruction.slice(index, end));
26
+ index = end;
27
+ }
28
+ return tokens;
29
+ }
30
+ function parseNumberedRefInstruction(instruction) {
31
+ if (instruction.length > MAX_FIELD_INSTRUCTION_CHARS) return null;
32
+ const tokens = tokenizeInstruction(instruction);
33
+ if (tokens === null || tokens.length < 3 || tokens.at(0)?.toUpperCase() !== "REF") return null;
34
+ const bookmark = tokens.at(1);
35
+ if (!bookmark || bookmark.length > MAX_BOOKMARK_NAME_CHARS || bookmark.startsWith("\\")) return null;
36
+ let numberSwitch = null;
37
+ for (let index = 2; index < tokens.length; index += 1) {
38
+ const token = tokens[index]?.toUpperCase();
39
+ if (token === "\\N" || token === "\\R" || token === "\\W") {
40
+ if (numberSwitch !== null) return null;
41
+ if (token === "\\N") numberSwitch = "n";
42
+ else if (token === "\\R") numberSwitch = "r";
43
+ else numberSwitch = "w";
44
+ continue;
45
+ }
46
+ if (token === "\\H") continue;
47
+ if (token === "\\*MERGEFORMAT") continue;
48
+ if (token === "\\*" && tokens[index + 1]?.toUpperCase() === "MERGEFORMAT") {
49
+ index += 1;
50
+ continue;
51
+ }
52
+ return null;
53
+ }
54
+ return numberSwitch === null ? null : {
55
+ bookmark,
56
+ numberSwitch
57
+ };
58
+ }
59
+ function trimTrailingPeriod(value) {
60
+ return value.length > 1 && value.endsWith(".") ? value.slice(0, -1) : value;
61
+ }
62
+ function firstReferencedLevel(template) {
63
+ let first = null;
64
+ for (const match of template.matchAll(/%(?<level>[1-9])/gu)) {
65
+ const level = Number.parseInt(match.groups?.["level"] ?? "", 10) - 1;
66
+ if (Number.isNaN(level)) continue;
67
+ first = first === null ? level : Math.min(first, level);
68
+ }
69
+ return first;
70
+ }
71
+ function numberTargetForParagraph(node, contextStreams, streams) {
72
+ const resolution = advanceVisibleListMarker(expectParagraphAttrs(node), streams);
73
+ let visibleTarget = null;
74
+ for (const advanced of resolution.advances) {
75
+ const target = numberTargetForAdvancedMarker(advanced.counterAttrs, advanced.marker, advanced.counterState, contextStreams[advanced.stream]);
76
+ if (advanced.stream === resolution.stream && advanced.counterAttrs === resolution.counterAttrs) visibleTarget = target;
77
+ }
78
+ return visibleTarget;
79
+ }
80
+ function numberTargetForAdvancedMarker(attrs, resolvedMarker, state, contexts) {
81
+ const numId = attrs.numPr?.numId;
82
+ if (numId === void 0 || numId === 0 || attrs.listIsBullet || attrs.listMarkerHidden || attrs.listNumFmt === "none" || !resolvedMarker) return null;
83
+ const marker = attrs.listMarkerAllCaps ? resolvedMarker.toLocaleUpperCase() : resolvedMarker;
84
+ const level = attrs.numPr?.ilvl ?? 0;
85
+ if (!Number.isInteger(level) || level < 0 || level > 8) return null;
86
+ const counters = state.counters.get(numId)?.slice(0, level + 1);
87
+ const levelFormats = attrs.listLevelNumFmts ?? (attrs.listNumFmt ? [attrs.listNumFmt] : void 0);
88
+ let renderedMarker = {
89
+ value: marker,
90
+ components: []
91
+ };
92
+ if (attrs.listMarker?.includes("%") && counters?.length === level + 1) {
93
+ const rendered = resolveListTemplateWithComponents({
94
+ template: attrs.listMarker,
95
+ counters,
96
+ levelFormats,
97
+ forceDecimal: attrs.listIsLegal
98
+ });
99
+ if ((attrs.listMarkerAllCaps ? rendered.value.toLocaleUpperCase() : rendered.value) === marker) renderedMarker = {
100
+ value: marker,
101
+ components: rendered.components
102
+ };
103
+ }
104
+ const abstractContexts = attrs.listAbstractNumId === void 0 ? void 0 : contexts.byAbstractNumId.get(attrs.listAbstractNumId);
105
+ const ownContexts = contexts.byNumId.get(numId) ?? [...abstractContexts ?? []];
106
+ const firstLevel = firstReferencedLevel(attrs.listMarker ?? "");
107
+ let renderedFull;
108
+ if (level === 0 || firstLevel === 0) renderedFull = renderedMarker;
109
+ else {
110
+ const parentLevel = (firstLevel ?? level) - 1;
111
+ const prefix = ownContexts[parentLevel] ?? abstractContexts?.[parentLevel];
112
+ renderedFull = prefix === void 0 ? null : {
113
+ value: `${prefix.value}${marker}`,
114
+ components: [...prefix.components, ...renderedMarker.components.map((component) => ({
115
+ level: component.level,
116
+ relativeStart: component.relativeStart + prefix.value.length,
117
+ start: component.start + prefix.value.length,
118
+ end: component.end + prefix.value.length
119
+ }))]
120
+ };
121
+ }
122
+ ownContexts[level] = renderedFull ?? void 0;
123
+ ownContexts.splice(level + 1);
124
+ contexts.byNumId.set(numId, ownContexts);
125
+ if (attrs.listAbstractNumId !== void 0) contexts.byAbstractNumId.set(attrs.listAbstractNumId, [...ownContexts]);
126
+ const path = counters?.length === level + 1 && counters.every(Number.isFinite) && renderedFull !== null ? {
127
+ scheme: attrs.listAbstractNumId === void 0 ? {
128
+ type: "instance",
129
+ id: numId
130
+ } : {
131
+ type: "abstract",
132
+ id: attrs.listAbstractNumId
133
+ },
134
+ counters,
135
+ rendered: renderedFull
136
+ } : null;
137
+ return {
138
+ current: trimTrailingPeriod(marker),
139
+ full: renderedFull === null ? null : trimTrailingPeriod(renderedFull.value),
140
+ path
141
+ };
142
+ }
143
+ function sameNumberingScheme(left, right) {
144
+ return left.type === right.type && left.id === right.id;
145
+ }
146
+ function relativeNumber(target, source) {
147
+ if (source === null || source.path === null || target.path === null) return target.full;
148
+ if (!sameNumberingScheme(source.path.scheme, target.path.scheme)) return target.full;
149
+ let sharedLevels = 0;
150
+ const comparedLevels = Math.min(source.path.counters.length, target.path.counters.length);
151
+ while (sharedLevels < comparedLevels && source.path.counters[sharedLevels] === target.path.counters[sharedLevels]) sharedLevels += 1;
152
+ if (sharedLevels === 0) return target.full;
153
+ if (sharedLevels >= target.path.counters.length) return null;
154
+ const firstRelativeComponent = target.path.rendered.components.find((component) => component.level >= sharedLevels);
155
+ if (firstRelativeComponent === void 0) return null;
156
+ const suffix = target.path.rendered.value.slice(firstRelativeComponent.relativeStart).replace(/^[\s,.:;/-]+/u, "");
157
+ return suffix.length === 0 ? null : trimTrailingPeriod(suffix);
158
+ }
159
+ function normalizeCachedResult(value) {
160
+ return value.replaceAll("\xA0", " ").replace(/\s+/gu, " ").trim();
161
+ }
162
+ function collectNumberedRefCandidates(doc, options) {
163
+ const fields = [];
164
+ doc.descendants((node) => {
165
+ if (node.type.name !== "field" && node.type.name !== "structuredField") return true;
166
+ if (fields.length >= MAX_NUMBERED_REF_FIELDS) return false;
167
+ const attrs = expectFieldAttrs(node);
168
+ if (attrs.fieldType !== "REF" || attrs.fldLock) return false;
169
+ const spec = parseNumberedRefInstruction(attrs.instruction);
170
+ if (spec !== null) fields.push({
171
+ node,
172
+ spec,
173
+ cached: attrs.displayText
174
+ });
175
+ return false;
176
+ });
177
+ if (fields.length === 0) return [];
178
+ const referencedBookmarks = new Set(fields.map(({ spec }) => spec.bookmark));
179
+ const fieldNodes = new Set(fields.map(({ node }) => node));
180
+ const sourceByField = /* @__PURE__ */ new WeakMap();
181
+ const targets = /* @__PURE__ */ new Map();
182
+ const streams = {
183
+ final: options.listCounterState ?? createListCounterState(),
184
+ original: options.originalListCounterState ?? createListCounterState()
185
+ };
186
+ const createContexts = () => ({
187
+ byNumId: /* @__PURE__ */ new Map(),
188
+ byAbstractNumId: /* @__PURE__ */ new Map()
189
+ });
190
+ const contextStreams = {
191
+ final: createContexts(),
192
+ original: createContexts()
193
+ };
194
+ doc.descendants((node) => {
195
+ if (node.type.name !== "paragraph") return true;
196
+ const target = numberTargetForParagraph(node, contextStreams, streams);
197
+ node.descendants((descendant) => {
198
+ if (descendant.type.name === "bookmarkBoundary") {
199
+ const attrs = expectBookmarkBoundaryAttrs(descendant);
200
+ if (attrs.type === "start" && targets.size < MAX_BOOKMARK_TARGETS && referencedBookmarks.has(attrs.name) && !targets.has(attrs.name)) targets.set(attrs.name, target);
201
+ return false;
202
+ }
203
+ if (descendant.type.name !== "field" && descendant.type.name !== "structuredField") return true;
204
+ if (fieldNodes.has(descendant)) sourceByField.set(descendant, target);
205
+ return true;
206
+ });
207
+ const paragraphAttrs = expectParagraphAttrs(node);
208
+ if (targets.size < MAX_BOOKMARK_TARGETS) {
209
+ for (const bookmark of paragraphAttrs.bookmarks ?? []) if (referencedBookmarks.has(bookmark.name) && !targets.has(bookmark.name)) targets.set(bookmark.name, target);
210
+ }
211
+ return false;
212
+ });
213
+ const candidates = [];
214
+ for (const field of fields) {
215
+ const target = targets.get(field.spec.bookmark);
216
+ if (target === void 0 || target === null) continue;
217
+ let value;
218
+ if (field.spec.numberSwitch === "n") value = target.current;
219
+ else if (field.spec.numberSwitch === "r") value = relativeNumber(target, sourceByField.get(field.node) ?? null);
220
+ else value = target.full;
221
+ if (value !== null) candidates.push({
222
+ node: field.node,
223
+ cached: field.cached,
224
+ value
225
+ });
226
+ }
227
+ return candidates;
228
+ }
229
+ const isCalibratedCandidate = ({ node, cached, value }) => {
230
+ const normalizedCache = normalizeCachedResult(cached);
231
+ const baseline = expectFieldAttrs(node)._numberedRefBaseline;
232
+ return normalizedCache.length === 0 || normalizedCache === normalizeCachedResult(value) || baseline !== void 0 && normalizedCache === normalizeCachedResult(baseline);
233
+ };
234
+ const mapDocument = (node, transform) => {
235
+ let mapped = node;
236
+ if (node.childCount > 0) {
237
+ const children = [];
238
+ let changed = false;
239
+ node.forEach((child) => {
240
+ const mappedChild = mapDocument(child, transform);
241
+ children.push(mappedChild);
242
+ changed ||= mappedChild !== child;
243
+ });
244
+ if (changed) mapped = node.copy(Fragment.fromArray(children));
245
+ }
246
+ return transform(mapped);
247
+ };
248
+ /** Stamp only proven numbered REF fields with clone-safe calibration state. */
249
+ function stampNumberedRefFieldBaselines(doc) {
250
+ const baselines = /* @__PURE__ */ new WeakMap();
251
+ let hasBaselines = false;
252
+ for (const candidate of collectNumberedRefCandidates(doc, {})) if (isCalibratedCandidate(candidate)) {
253
+ baselines.set(candidate.node, candidate.cached);
254
+ hasBaselines = true;
255
+ }
256
+ if (!hasBaselines) return doc;
257
+ return mapDocument(doc, (node) => {
258
+ const baseline = baselines.get(node);
259
+ if (baseline === void 0 || node.type.name !== "field" && node.type.name !== "structuredField") return node;
260
+ return node.type.create({
261
+ ...node.attrs,
262
+ _numberedRefBaseline: baseline
263
+ }, node.content, node.marks);
264
+ });
265
+ }
266
+ /**
267
+ * Resolve bounded, numbered REF fields against bookmarks in the ProseMirror body story.
268
+ * Unsupported or uncalibrated fields are absent from the returned map and retain their cache.
269
+ */
270
+ function resolveNumberedRefFields(doc, options = {}) {
271
+ const results = /* @__PURE__ */ new Map();
272
+ for (const candidate of collectNumberedRefCandidates(doc, options)) if (isCalibratedCandidate(candidate)) results.set(candidate.node, candidate.value);
273
+ return results;
274
+ }
275
+ //#endregion
276
+ export { parseNumberedRefInstruction, resolveNumberedRefFields, stampNumberedRefFieldBaselines };
@@ -3,9 +3,15 @@
3
3
  function getVanillaNodeText(node) {
4
4
  const parts = [];
5
5
  node.descendants((child) => {
6
- if (!child.isText || !child.text) return true;
7
6
  if (child.marks.some((m) => m.type.name === "insertion")) return false;
8
- parts.push(child.text);
7
+ if (child.isText && child.text) {
8
+ parts.push(child.text);
9
+ return false;
10
+ }
11
+ if (child.isLeaf && child.textContent) {
12
+ parts.push(child.textContent);
13
+ return false;
14
+ }
9
15
  return true;
10
16
  });
11
17
  return parts.join("");
@@ -15,11 +21,18 @@ function getVanillaTextBetween(doc, from, to) {
15
21
  if (from >= to) return "";
16
22
  const parts = [];
17
23
  doc.nodesBetween(from, to, (child, pos) => {
18
- if (!child.isText || !child.text) return;
19
- if (child.marks.some((m) => m.type.name === "insertion")) return;
20
- const start = Math.max(from, pos);
21
- const end = Math.min(to, pos + child.text.length);
22
- if (start < end) parts.push(child.text.slice(start - pos, end - pos));
24
+ if (child.marks.some((m) => m.type.name === "insertion")) return false;
25
+ if (child.isText && child.text) {
26
+ const start = Math.max(from, pos);
27
+ const end = Math.min(to, pos + child.text.length);
28
+ if (start < end) parts.push(child.text.slice(start - pos, end - pos));
29
+ return false;
30
+ }
31
+ if (child.isLeaf && child.textContent) {
32
+ parts.push(child.textContent);
33
+ return false;
34
+ }
35
+ return true;
23
36
  });
24
37
  return parts.join("");
25
38
  }
@@ -41,25 +54,51 @@ function findTextInPmParagraph(doc, paragraphFrom, paragraphTo, searchText) {
41
54
  let fullText = "";
42
55
  const textPositions = [];
43
56
  doc.nodesBetween(paragraphFrom, paragraphTo, (node, pos) => {
44
- if (!node.isText || !node.text) return;
45
- if (node.marks.some((m) => m.type.name === "insertion")) return;
46
- textPositions.push({
47
- pos,
48
- len: node.text.length
49
- });
50
- fullText += node.text;
57
+ if (node.marks.some((m) => m.type.name === "insertion")) return false;
58
+ if (node.isText && node.text) {
59
+ textPositions.push({
60
+ text: node.text,
61
+ pos,
62
+ pmLength: node.text.length,
63
+ atomic: false
64
+ });
65
+ fullText += node.text;
66
+ return false;
67
+ }
68
+ if (node.isLeaf && node.textContent) {
69
+ textPositions.push({
70
+ text: node.textContent,
71
+ pos,
72
+ pmLength: node.nodeSize,
73
+ atomic: true
74
+ });
75
+ fullText += node.textContent;
76
+ return false;
77
+ }
78
+ return true;
51
79
  });
52
80
  const firstMatch = fullText.indexOf(searchText);
53
81
  if (firstMatch === -1) return null;
54
82
  if (fullText.indexOf(searchText, firstMatch + 1) !== -1) return null;
83
+ const matchEnd = firstMatch + searchText.length;
84
+ let segmentStart = 0;
85
+ for (const position of textPositions) {
86
+ const segmentEnd = segmentStart + position.text.length;
87
+ if (position.atomic && (segmentStart < firstMatch && firstMatch < segmentEnd || segmentStart < matchEnd && matchEnd < segmentEnd)) return null;
88
+ segmentStart = segmentEnd;
89
+ }
55
90
  let charOffset = 0;
56
91
  let fromPos = paragraphFrom;
57
92
  let toPos = paragraphFrom;
58
93
  for (const tp of textPositions) {
59
- const segEnd = charOffset + tp.len;
60
- if (charOffset <= firstMatch && firstMatch < segEnd) fromPos = tp.pos + (firstMatch - charOffset);
94
+ const segEnd = charOffset + tp.text.length;
95
+ if (charOffset <= firstMatch && firstMatch < segEnd) {
96
+ const localOffset = firstMatch - charOffset;
97
+ fromPos = tp.atomic ? tp.pos : tp.pos + localOffset;
98
+ }
61
99
  if (charOffset <= firstMatch + searchText.length && firstMatch + searchText.length <= segEnd) {
62
- toPos = tp.pos + (firstMatch + searchText.length - charOffset);
100
+ const localOffset = firstMatch + searchText.length - charOffset;
101
+ toPos = tp.atomic ? tp.pos + tp.pmLength : tp.pos + localOffset;
63
102
  break;
64
103
  }
65
104
  charOffset = segEnd;
@@ -36,7 +36,7 @@ const buildMatches = (doc, terms) => {
36
36
  let match = regex.exec(joined);
37
37
  while (match !== null) {
38
38
  const from = offsetToDocPos(chunks, match.index);
39
- const to = offsetToDocPos(chunks, match.index + match[0].length);
39
+ const to = offsetToDocPos(chunks, match.index + match[0].length, "end");
40
40
  matches.push({
41
41
  from,
42
42
  to,
@@ -4,6 +4,8 @@ type TextChunk = {
4
4
  text: string;
5
5
  /** PM doc position where this chunk's first char lives. */
6
6
  start: number;
7
+ /** PM position after this chunk; differs from text length for atomic fields. */
8
+ end?: number;
7
9
  };
8
10
  /**
9
11
  * Collect every block-level node's text content as an array of
@@ -12,7 +14,7 @@ type TextChunk = {
12
14
  */
13
15
  declare const collectBlockChunks: (doc: Node) => TextChunk[][];
14
16
  /** Map a joined-string offset back to its PM doc position. */
15
- declare const offsetToDocPos: (chunks: TextChunk[], offset: number) => number;
17
+ declare const offsetToDocPos: (chunks: TextChunk[], offset: number, bias?: "start" | "end") => number;
16
18
  /** Join a block's chunks into the single string callers scan. */
17
19
  declare const joinChunks: (chunks: TextChunk[]) => string;
18
20
  //#endregion
@@ -10,10 +10,24 @@ const collectBlockChunks = (doc) => {
10
10
  if (node.isTextblock) {
11
11
  const chunks = [];
12
12
  node.descendants((child, offset) => {
13
- if (child.isText && child.text !== void 0) chunks.push({
14
- text: child.text,
15
- start: pos + 1 + offset
16
- });
13
+ if (child.isText && child.text !== void 0) {
14
+ const start = pos + 1 + offset;
15
+ chunks.push({
16
+ text: child.text,
17
+ start,
18
+ end: start + child.text.length
19
+ });
20
+ return false;
21
+ }
22
+ if (child.isLeaf && child.textContent) {
23
+ const start = pos + 1 + offset;
24
+ chunks.push({
25
+ text: child.textContent,
26
+ start,
27
+ end: start + child.nodeSize
28
+ });
29
+ return false;
30
+ }
17
31
  return true;
18
32
  });
19
33
  if (chunks.length > 0) blocks.push(chunks);
@@ -24,14 +38,21 @@ const collectBlockChunks = (doc) => {
24
38
  return blocks;
25
39
  };
26
40
  /** Map a joined-string offset back to its PM doc position. */
27
- const offsetToDocPos = (chunks, offset) => {
41
+ const offsetToDocPos = (chunks, offset, bias = "start") => {
28
42
  let consumed = 0;
29
43
  for (const chunk of chunks) {
30
- if (offset <= consumed + chunk.text.length) return chunk.start + (offset - consumed);
44
+ if (offset <= consumed + chunk.text.length) {
45
+ const localOffset = offset - consumed;
46
+ const end = chunk.end ?? chunk.start + chunk.text.length;
47
+ if (end - chunk.start === chunk.text.length) return chunk.start + localOffset;
48
+ if (localOffset === 0) return chunk.start;
49
+ if (localOffset === chunk.text.length || bias === "end") return end;
50
+ return chunk.start;
51
+ }
31
52
  consumed += chunk.text.length;
32
53
  }
33
54
  const last = chunks.at(-1);
34
- return last ? last.start + last.text.length : 0;
55
+ return last ? last.end ?? last.start + last.text.length : 0;
35
56
  };
36
57
  /** Join a block's chunks into the single string callers scan. */
37
58
  const joinChunks = (chunks) => chunks.map((c) => c.text).join("");