@stll/folio-core 0.42.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 (46) hide show
  1. package/dist/compat/eigenpal.d.ts +2 -2
  2. package/dist/controller/layoutPipeline.d.ts +2 -1
  3. package/dist/controller/layoutPipeline.js +5 -2
  4. package/dist/controller/layoutSession.d.ts +2 -1
  5. package/dist/controller/layoutSession.js +1 -0
  6. package/dist/docx/blockContentParser.js +2 -1
  7. package/dist/docx/commentIdNormalization.d.ts +8 -0
  8. package/dist/docx/commentIdNormalization.js +16 -0
  9. package/dist/docx/defaultParagraphStyle.d.ts +22 -0
  10. package/dist/docx/defaultParagraphStyle.js +32 -0
  11. package/dist/docx/footnoteParser.js +2 -0
  12. package/dist/docx/headerFooterRefParser.d.ts +12 -1
  13. package/dist/docx/headerFooterRefParser.js +9 -2
  14. package/dist/docx/numberingParser.js +2 -1
  15. package/dist/docx/numberingReference.d.ts +21 -0
  16. package/dist/docx/numberingReference.js +21 -0
  17. package/dist/docx/numberingReferenceNormalization.d.ts +11 -2
  18. package/dist/docx/numberingReferenceNormalization.js +32 -9
  19. package/dist/docx/paragraphParser.js +3 -2
  20. package/dist/docx/parser.js +10 -2
  21. package/dist/docx/rezip.js +22 -5
  22. package/dist/docx/server/createBilingualDocument.js +5 -4
  23. package/dist/docx/styleParser.js +2 -2
  24. package/dist/index.d.ts +2 -2
  25. package/dist/internal/pageBreakRunSourceDescendantIndex.d.ts +2 -0
  26. package/dist/internal/pageBreakRunSourceDescendantIndex.js +7 -5
  27. package/dist/layout-bridge/convert/templatePreviewFlow.d.ts +19 -11
  28. package/dist/layout-bridge/convert/templatePreviewFlow.js +103 -38
  29. package/dist/layout-bridge/convert/toFlowBlocks.js +12 -3
  30. package/dist/prosemirror/commands/pageBreak.js +12 -1
  31. package/dist/prosemirror/conversion/fromProseDoc.js +2 -1
  32. package/dist/prosemirror/conversion/toProseDoc.js +27 -12
  33. package/dist/prosemirror/extensions/features/ListExtension.js +42 -4
  34. package/dist/prosemirror/listMarker.js +2 -1
  35. package/dist/prosemirror/numberedRefFields.js +2 -1
  36. package/dist/prosemirror/pageBreakRunProjection.d.ts +11 -3
  37. package/dist/prosemirror/pageBreakRunProjection.js +16 -8
  38. package/dist/prosemirror/plugins/templatePreviewValues.d.ts +42 -1
  39. package/dist/prosemirror/plugins/templatePreviewValues.js +217 -14
  40. package/dist/prosemirror/styles/resolvedStyleAttrs.js +2 -1
  41. package/dist/prosemirror/styles/styleResolver.js +3 -6
  42. package/dist/prosemirror/utils/visualLineNavigation.d.ts +22 -2
  43. package/dist/prosemirror/utils/visualLineNavigation.js +80 -63
  44. package/dist/style-engine/styleEngine.d.ts +1 -1
  45. package/dist/style-sets/extract.js +57 -11
  46. package/package.json +2 -1
@@ -4,10 +4,10 @@ const PAGE_BREAK_RUN_PARAGRAPH_PROJECTION_MESSAGES = {
4
4
  borders: "A bordered paragraph containing an explicit page-break run cannot be projected",
5
5
  frame: "A framed paragraph containing an explicit page-break run cannot be projected",
6
6
  outline: "An outline paragraph containing an explicit page-break run cannot be projected",
7
- textBoxAnchor: "A paragraph containing both an explicit page-break run and a text-box anchor cannot be projected"
7
+ textBoxAnchor: "A paragraph whose text-box anchor follows an explicit page-break run cannot be projected"
8
8
  };
9
9
  /** Keep source-import and ProseMirror-layout ownership decisions on one predicate. */
10
- const pageBreakRunParagraphProjectionDispositionForFeatures = ({ attrs, effectiveFrame, hasTextBoxAnchor }) => {
10
+ const pageBreakRunParagraphProjectionDispositionForFeatures = ({ attrs, effectiveFrame, textBoxAnchorAfterPageBreak }) => {
11
11
  if (effectiveFrame !== void 0 && effectiveFrame.dropCap !== "drop" && effectiveFrame.dropCap !== "margin") return {
12
12
  status: "unsupported",
13
13
  reason: "frame",
@@ -23,7 +23,7 @@ const pageBreakRunParagraphProjectionDispositionForFeatures = ({ attrs, effectiv
23
23
  reason: "borders",
24
24
  message: PAGE_BREAK_RUN_PARAGRAPH_PROJECTION_MESSAGES.borders
25
25
  };
26
- if (hasTextBoxAnchor) return {
26
+ if (textBoxAnchorAfterPageBreak) return {
27
27
  status: "unsupported",
28
28
  reason: "textBoxAnchor",
29
29
  message: PAGE_BREAK_RUN_PARAGRAPH_PROJECTION_MESSAGES.textBoxAnchor
@@ -32,15 +32,23 @@ const pageBreakRunParagraphProjectionDispositionForFeatures = ({ attrs, effectiv
32
32
  };
33
33
  const pageBreakRunParagraphProjectionDisposition = (paragraph) => {
34
34
  const attrs = expectParagraphAttrs(paragraph);
35
- let hasTextBoxAnchor = false;
36
- paragraph.descendants((descendant) => {
37
- hasTextBoxAnchor ||= descendant.type.name === "textBoxAnchor";
38
- return !hasTextBoxAnchor;
35
+ let firstPageBreakPos;
36
+ let textBoxAnchorAfterPageBreak = false;
37
+ paragraph.descendants((descendant, pos) => {
38
+ if (descendant.type.name === "pageBreakRun") {
39
+ firstPageBreakPos ??= pos;
40
+ return false;
41
+ }
42
+ if (descendant.type.name === "textBoxAnchor" && firstPageBreakPos !== void 0) {
43
+ textBoxAnchorAfterPageBreak = true;
44
+ return false;
45
+ }
46
+ return !textBoxAnchorAfterPageBreak;
39
47
  });
40
48
  return pageBreakRunParagraphProjectionDispositionForFeatures({
41
49
  attrs,
42
50
  effectiveFrame: attrs._originalFormatting?.frame,
43
- hasTextBoxAnchor
51
+ textBoxAnchorAfterPageBreak
44
52
  });
45
53
  };
46
54
  //#endregion
@@ -25,6 +25,19 @@ type TemplatePreviewValues = {
25
25
  values: Record<string, TemplatePreviewValue>;
26
26
  /** `highlighted` marks substitutions with the preview accent. */
27
27
  mode: "highlighted" | "plain";
28
+ /**
29
+ * `if` expression → whether its block applies. The key is the expression
30
+ * exactly as written between `{% if` and `%}`, trimmed; a tag that carries a
31
+ * filter chain (`{% if consented | checkbox | label("…") %}`) also answers to
32
+ * the bare path in front of the chain, so a host that keys by field path need
33
+ * not repeat the chain. An expression the map does not mention is left as
34
+ * authored. The host decides truthiness: folio evaluates nothing.
35
+ *
36
+ * `false` hides the block, opener through closer. `true` keeps the body and,
37
+ * in `plain` mode, drops the block's tag paragraphs so the reader sees what
38
+ * will be generated; `highlighted` mode keeps them.
39
+ */
40
+ conditions?: Record<string, boolean>;
28
41
  };
29
42
  /**
30
43
  * One `{{path}}` marker matched by an active preview value. Exposed on
@@ -41,9 +54,37 @@ type TemplatePreviewEntry = {
41
54
  /** The typed value displayed in place of the marker. */
42
55
  value: TemplatePreviewValue;
43
56
  };
57
+ /**
58
+ * One span the preview hides: a `false` block from its opener through its
59
+ * closer, or a single directive tag of a `true` block in `plain` mode. Exposed
60
+ * on the plugin state beside {@link TemplatePreviewEntry} so a surface that
61
+ * never sees PM decorations can drop the same spans.
62
+ */
63
+ type TemplatePreviewHiddenRange = {
64
+ /** Inclusive PM doc position of the span start. */
65
+ from: number;
66
+ /** Exclusive PM doc position of the span end. */
67
+ to: number;
68
+ /** The `if` expression of the block this span belongs to, as authored. */
69
+ expr: string;
70
+ };
71
+ /**
72
+ * Whether a hidden span swallows a block whole, i.e. covers every position of
73
+ * its content, leaving it nothing to render. `pmStart`/`pmEnd` are the block
74
+ * node's own positions, so its content is `[pmStart + 1, pmEnd - 1)`.
75
+ *
76
+ * The decoration path and the paged flow both drop a block on this predicate,
77
+ * so the two surfaces hide exactly the same blocks; a block only partly covered
78
+ * fails it and keeps its text, and the covered span alone is hidden.
79
+ */
80
+ declare const templatePreviewHidesWholeBlock: (range: TemplatePreviewHiddenRange, block: {
81
+ pmStart: number;
82
+ pmEnd: number;
83
+ }) => boolean;
44
84
  type TemplatePreviewState = {
45
85
  preview: TemplatePreviewValues | null;
46
86
  entries: TemplatePreviewEntry[];
87
+ hidden: TemplatePreviewHiddenRange[];
47
88
  decorationSet: DecorationSet;
48
89
  };
49
90
  declare const templatePreviewValuesKey: PluginKey<TemplatePreviewState>;
@@ -51,4 +92,4 @@ declare function createTemplatePreviewValuesPlugin(): Plugin<TemplatePreviewStat
51
92
  /** Push (or clear, with `null`) the live fill preview into the editor. */
52
93
  declare const setTemplatePreviewValues: (view: EditorView, preview: TemplatePreviewValues | null) => void;
53
94
  //#endregion
54
- export { TemplatePreviewEntry, TemplatePreviewSpan, TemplatePreviewValue, TemplatePreviewValues, createTemplatePreviewValuesPlugin, setTemplatePreviewValues, templatePreviewValueFingerprint, templatePreviewValueText, templatePreviewValuesKey };
95
+ export { TemplatePreviewEntry, TemplatePreviewHiddenRange, TemplatePreviewSpan, TemplatePreviewValue, TemplatePreviewValues, createTemplatePreviewValuesPlugin, setTemplatePreviewValues, templatePreviewHidesWholeBlock, templatePreviewValueFingerprint, templatePreviewValueText, templatePreviewValuesKey };
@@ -1,6 +1,7 @@
1
1
  import { scanDirectives } from "./templateDirectives.js";
2
2
  import { Plugin, PluginKey } from "prosemirror-state";
3
3
  import { Decoration, DecorationSet } from "prosemirror-view";
4
+ import { classifyMarker, isFieldPath } from "@stll/template-conditions";
4
5
  //#region src/prosemirror/plugins/templatePreviewValues.ts
5
6
  /** Concatenated plain text of a preview value. */
6
7
  const templatePreviewValueText = (value) => typeof value === "string" ? value : value.runs.map((run) => run.text).join("");
@@ -11,6 +12,20 @@ const templatePreviewValueFingerprint = (value) => {
11
12
  if (typeof value === "string") return value;
12
13
  return value.runs.map((run) => `${run.bold === true ? "b" : ""}${run.italic === true ? "i" : ""}:${run.text}`).join("\0");
13
14
  };
15
+ /**
16
+ * Whether a hidden span swallows a block whole, i.e. covers every position of
17
+ * its content, leaving it nothing to render. `pmStart`/`pmEnd` are the block
18
+ * node's own positions, so its content is `[pmStart + 1, pmEnd - 1)`.
19
+ *
20
+ * The decoration path and the paged flow both drop a block on this predicate,
21
+ * so the two surfaces hide exactly the same blocks; a block only partly covered
22
+ * fails it and keeps its text, and the covered span alone is hidden.
23
+ */
24
+ const templatePreviewHidesWholeBlock = (range, block) => range.from <= block.pmStart + 1 && range.to >= block.pmEnd - 1;
25
+ /** Hides the marker text a preview value stands in for. */
26
+ const ORIGINAL_CLASS = "folio-template-preview-original";
27
+ /** Hides a conditional block the host reported as not applying. */
28
+ const HIDDEN_CLASS = "folio-template-preview-hidden";
14
29
  const KEY_HOLDER_SYMBOL = Symbol.for("stll.folio.templatePreviewValuesKey");
15
30
  const keyHolder = globalThis;
16
31
  const templatePreviewValuesKey = keyHolder[KEY_HOLDER_SYMBOL] ?? (keyHolder[KEY_HOLDER_SYMBOL] = new PluginKey("templatePreviewValues"));
@@ -29,6 +44,13 @@ function buildSpanNode(span) {
29
44
  }
30
45
  return node;
31
46
  }
47
+ /**
48
+ * The widget standing in for a marker. A value carrying newlines breaks its
49
+ * lines inside the paragraph, not into new ones: here through the widget class's
50
+ * `white-space: pre-wrap`, and on the paged canvas through the line break runs
51
+ * `applyTemplatePreviewToBlocks` emits. The two must keep agreeing — an inline
52
+ * `{{ path }}` placeholder cannot introduce paragraphs on either surface.
53
+ */
32
54
  function buildValueWidget(value, mode) {
33
55
  return () => {
34
56
  const span = document.createElement("span");
@@ -42,13 +64,142 @@ function buildValueWidget(value, mode) {
42
64
  return span;
43
65
  };
44
66
  }
45
- function collectPreviewEntries(doc, preview) {
46
- if (!preview || Object.keys(preview.values).length === 0) return [];
67
+ /** Loop alias the bare-path probe binds. Any identifier does; it is discarded. */
68
+ const CHAIN_PROBE_ALIAS = "__folioCondition";
69
+ /** A field path opens with a letter or underscore, so `9x` and `-x` are not one. */
70
+ const FIELD_PATH_HEAD_RE = /^[\p{L}_]/u;
71
+ /**
72
+ * The bare field path a condition's filter chain hangs off, or undefined when
73
+ * the expression is not a path plus a chain.
74
+ *
75
+ * A host may write the tag the way `{% for x in xs | chain %}` already carries
76
+ * one — `{% if buyer_is_a_consumer | checkbox | ai("Is the buyer …") %}` — while
77
+ * keying `conditions` by the bare path. Splitting on `|` would cut a quoted
78
+ * argument such as `label("a | b")` in half, so the expression is classified as
79
+ * that very `for` chain instead and the grammar's own argument-aware scan
80
+ * reports the path. An expression that is not a path plus a chain of known
81
+ * filters (`a and b`, `items|length > 0`, `ai("a|b")`) classifies as nothing,
82
+ * which is what keeps a real expression on exact matching.
83
+ */
84
+ const filterChainPath = (expr) => {
85
+ const meta = classifyMarker(`for ${CHAIN_PROBE_ALIAS} in ${expr}`, "statement");
86
+ if (meta?.kind !== "for" || !isFieldPath(meta.path) || !FIELD_PATH_HEAD_RE.test(meta.path)) return;
87
+ return meta.path;
88
+ };
89
+ /**
90
+ * The verdict stored under one key. Only a key the host set itself, holding a
91
+ * real boolean, counts: an inherited property (`constructor`, or a boolean the
92
+ * host's prototype carries) and a value from an untyped host are read as
93
+ * silence rather than as a verdict.
94
+ */
95
+ const ownVerdict = (conditions, key) => {
96
+ if (!Object.hasOwn(conditions, key)) return;
97
+ const value = conditions[key];
98
+ return typeof value === "boolean" ? value : void 0;
99
+ };
100
+ /**
101
+ * The host's verdict on one condition: keyed by the expression as written, else
102
+ * by the bare path its filter chain hangs off. `undefined` means the host said
103
+ * nothing about this block, which leaves it exactly as authored.
104
+ */
105
+ const conditionVerdict = (conditions, expr) => {
106
+ const exact = ownVerdict(conditions, expr);
107
+ if (exact !== void 0) return exact;
108
+ const path = filterChainPath(expr);
109
+ return path === void 0 ? void 0 : ownVerdict(conditions, path);
110
+ };
111
+ /**
112
+ * The spans the host's verdicts hide, in document order and without their
113
+ * nested hidden blocks: hiding a block already hides everything inside it.
114
+ *
115
+ * A `false` block is hidden whole, opener through closer. A `true` block keeps
116
+ * its body, and in `plain` mode loses its tag paragraphs — opener, any
117
+ * `{% elif %}` / `{% else %}`, closer — so the reader sees the document as it
118
+ * will be generated rather than its scaffolding. `highlighted` mode exists to
119
+ * show that scaffolding, so it keeps the tags. A block no verdict mentions is
120
+ * left exactly as authored.
121
+ *
122
+ * Openers and closers pair off a kind-aware stack — `{% endif %}` closes the
123
+ * nearest open `{% if %}`, `{% endfor %}` the nearest `{% for %}` — so a
124
+ * mid-edit template with an unpaired opener or a stray closer hides nothing
125
+ * rather than guessing a span. Inline markers pair like block ones: the fill
126
+ * engine resolves an inline conditional within its paragraph, so the preview
127
+ * follows. An `{% elif %}` or `{% else %}` branch of a `false` block sits
128
+ * inside its span and is hidden with it.
129
+ */
130
+ function collectHiddenRanges(ranges, conditions, mode) {
131
+ if (conditions === void 0) return [];
132
+ const open = [];
133
+ const paired = [];
134
+ for (const range of [...ranges].sort((a, b) => a.from - b.from)) {
135
+ if (range.kind === "if" || range.kind === "for") {
136
+ open.push({
137
+ opener: range,
138
+ branches: []
139
+ });
140
+ continue;
141
+ }
142
+ if (range.kind === "elif" || range.kind === "else") {
143
+ const innermost = open.at(-1);
144
+ if (innermost?.opener.kind === "if") innermost.branches.push(range);
145
+ continue;
146
+ }
147
+ if (range.kind !== "endif" && range.kind !== "endfor") continue;
148
+ const wanted = range.kind === "endif" ? "if" : "for";
149
+ let block;
150
+ for (let index = open.length - 1; index >= 0; index -= 1) {
151
+ const candidate = open[index];
152
+ if (candidate?.opener.kind === wanted) {
153
+ block = candidate;
154
+ open.length = index;
155
+ break;
156
+ }
157
+ }
158
+ if (block === void 0 || block.opener.kind !== "if") continue;
159
+ const { opener, branches } = block;
160
+ const verdict = conditionVerdict(conditions, opener.expr);
161
+ if (verdict === void 0) continue;
162
+ if (!verdict) {
163
+ paired.push({
164
+ from: opener.from,
165
+ to: range.to,
166
+ expr: opener.expr
167
+ });
168
+ continue;
169
+ }
170
+ if (mode !== "plain") continue;
171
+ paired.push({
172
+ from: opener.from,
173
+ to: opener.to,
174
+ expr: opener.expr
175
+ });
176
+ for (const branch of branches) paired.push({
177
+ from: branch.from,
178
+ to: branch.to,
179
+ expr: opener.expr
180
+ });
181
+ paired.push({
182
+ from: range.from,
183
+ to: range.to,
184
+ expr: opener.expr
185
+ });
186
+ }
187
+ paired.sort((a, b) => a.from - b.from || b.to - a.to);
188
+ const outermost = [];
189
+ for (const range of paired) {
190
+ const previous = outermost.at(-1);
191
+ if (previous && range.to <= previous.to) continue;
192
+ outermost.push(range);
193
+ }
194
+ return outermost;
195
+ }
196
+ function collectPreviewEntries(ranges, values, hidden) {
47
197
  const entries = [];
48
- for (const range of scanDirectives(doc)) {
198
+ for (const range of ranges) {
49
199
  if (range.kind !== "placeholder" && range.kind !== "clause") continue;
50
- const value = preview.values[range.expr];
200
+ const value = values[range.expr];
51
201
  if (value === void 0 || templatePreviewValueText(value) === "") continue;
202
+ if (hidden.some((block) => range.from >= block.from && range.from < block.to)) continue;
52
203
  entries.push({
53
204
  from: range.from,
54
205
  to: range.to,
@@ -58,10 +209,59 @@ function collectPreviewEntries(doc, preview) {
58
209
  }
59
210
  return entries;
60
211
  }
61
- function buildDecorationSet(doc, entries, mode) {
62
- if (entries.length === 0) return DecorationSet.empty;
212
+ function projectPreview(doc, preview) {
213
+ if (!preview) return {
214
+ entries: [],
215
+ hidden: []
216
+ };
217
+ const hasValues = Object.keys(preview.values).length > 0;
218
+ const hasConditions = preview.conditions !== void 0 && Object.keys(preview.conditions).length > 0;
219
+ if (!hasValues && !hasConditions) return {
220
+ entries: [],
221
+ hidden: []
222
+ };
223
+ const ranges = scanDirectives(doc);
224
+ const hidden = collectHiddenRanges(ranges, preview.conditions, preview.mode);
225
+ return {
226
+ entries: hasValues ? collectPreviewEntries(ranges, preview.values, hidden) : [],
227
+ hidden
228
+ };
229
+ }
230
+ /**
231
+ * Hide one span: a block the span covers whole is hidden as a node, leaving no
232
+ * empty line behind; a block it covers in part is hidden over the covered text
233
+ * only, so an inline `{% if %}` in a paragraph of running text takes its own
234
+ * span and nothing else.
235
+ */
236
+ function pushHiddenDecorations(doc, range, out) {
237
+ const { from, to } = range;
238
+ doc.nodesBetween(from, to, (node, pos) => {
239
+ if (!node.isBlock) return false;
240
+ const nodeEnd = pos + node.nodeSize;
241
+ if (templatePreviewHidesWholeBlock(range, {
242
+ pmStart: pos,
243
+ pmEnd: nodeEnd
244
+ })) {
245
+ out.push(Decoration.node(pos, nodeEnd, { class: HIDDEN_CLASS }));
246
+ return false;
247
+ }
248
+ const contentFrom = pos + 1;
249
+ const contentTo = nodeEnd - 1;
250
+ if (!node.isTextblock) return true;
251
+ const spanFrom = Math.max(from, contentFrom);
252
+ const spanTo = Math.min(to, contentTo);
253
+ if (spanTo > spanFrom) out.push(Decoration.inline(spanFrom, spanTo, { class: HIDDEN_CLASS }, {
254
+ inclusiveStart: false,
255
+ inclusiveEnd: false
256
+ }));
257
+ return false;
258
+ });
259
+ }
260
+ function buildDecorationSet(doc, { entries, hidden }, mode) {
261
+ if (entries.length === 0 && hidden.length === 0) return DecorationSet.empty;
63
262
  const decorations = [];
64
- for (const entry of entries) decorations.push(Decoration.inline(entry.from, entry.to, { class: "folio-template-preview-original" }, {
263
+ for (const range of hidden) pushHiddenDecorations(doc, range, decorations);
264
+ for (const entry of entries) decorations.push(Decoration.inline(entry.from, entry.to, { class: ORIGINAL_CLASS }, {
65
265
  inclusiveStart: false,
66
266
  inclusiveEnd: false
67
267
  }), Decoration.widget(entry.from, buildValueWidget(entry.value, mode), {
@@ -80,25 +280,28 @@ function createTemplatePreviewValuesPlugin() {
80
280
  return {
81
281
  preview: null,
82
282
  entries: [],
283
+ hidden: [],
83
284
  decorationSet: DecorationSet.empty
84
285
  };
85
286
  },
86
287
  apply(tr, prev, _oldState, newState) {
87
288
  const meta = tr.getMeta(templatePreviewValuesKey);
88
289
  if (meta !== void 0) {
89
- const entries = collectPreviewEntries(newState.doc, meta.preview);
290
+ const projection = projectPreview(newState.doc, meta.preview);
90
291
  return {
91
292
  preview: meta.preview,
92
- entries,
93
- decorationSet: buildDecorationSet(newState.doc, entries, meta.preview?.mode ?? "plain")
293
+ entries: projection.entries,
294
+ hidden: projection.hidden,
295
+ decorationSet: buildDecorationSet(newState.doc, projection, meta.preview?.mode ?? "plain")
94
296
  };
95
297
  }
96
298
  if (tr.docChanged && prev.preview) {
97
- const entries = collectPreviewEntries(newState.doc, prev.preview);
299
+ const projection = projectPreview(newState.doc, prev.preview);
98
300
  return {
99
301
  preview: prev.preview,
100
- entries,
101
- decorationSet: buildDecorationSet(newState.doc, entries, prev.preview.mode)
302
+ entries: projection.entries,
303
+ hidden: projection.hidden,
304
+ decorationSet: buildDecorationSet(newState.doc, projection, prev.preview.mode)
102
305
  };
103
306
  }
104
307
  return prev;
@@ -114,4 +317,4 @@ const setTemplatePreviewValues = (view, preview) => {
114
317
  view.dispatch(view.state.tr.setMeta(templatePreviewValuesKey, { preview }));
115
318
  };
116
319
  //#endregion
117
- export { createTemplatePreviewValuesPlugin, setTemplatePreviewValues, templatePreviewValueFingerprint, templatePreviewValueText, templatePreviewValuesKey };
320
+ export { createTemplatePreviewValuesPlugin, setTemplatePreviewValues, templatePreviewHidesWholeBlock, templatePreviewValueFingerprint, templatePreviewValueText, templatePreviewValuesKey };
@@ -1,4 +1,5 @@
1
1
  import { computeListRendering, numberingLevelHasMarkerSlot } from "../../docx/numberingParser.js";
2
+ import { isNumberingReference } from "../../docx/numberingReference.js";
2
3
  import { tableOfContentsStyleLevel } from "../../utils/tableOfContentsStyle.js";
3
4
  import { setAutospacingBaseValue } from "../autospacingBase.js";
4
5
  import { CLEARED_LIST_RENDERING_ATTRS } from "../listMarker.js";
@@ -74,7 +75,7 @@ function autospacingBaseFromResolvedParagraphFormatting(ppr) {
74
75
  */
75
76
  function listAttrsFromResolvedStyle(resolved, numbering) {
76
77
  const numPr = resolved.paragraphFormatting?.numPr;
77
- if (!numPr || numPr.numId === void 0 || numPr.numId === 0) return null;
78
+ if (!numPr || !isNumberingReference(numPr.numId)) return null;
78
79
  const attrs = listAttrsFromNumbering({
79
80
  numId: numPr.numId,
80
81
  ilvl: numPr.ilvl ?? 0
@@ -1,3 +1,4 @@
1
+ import { BUILT_IN_DEFAULT_PARAGRAPH_FORMATTING, resolveDefaultParagraphStyle } from "../../docx/defaultParagraphStyle.js";
1
2
  import { mergeParagraphFormatting } from "../../utils/paragraphFormattingMerge.js";
2
3
  import { cascadeStyleTextFormatting } from "./styleToggleCascade.js";
3
4
  //#region src/prosemirror/styles/styleResolver.ts
@@ -19,11 +20,7 @@ const BUILTIN_NORMAL_STYLE = {
19
20
  type: "paragraph",
20
21
  name: "Normal",
21
22
  default: true,
22
- pPr: {
23
- spaceAfter: 160,
24
- lineSpacing: 259,
25
- lineSpacingRule: "auto"
26
- }
23
+ pPr: BUILT_IN_DEFAULT_PARAGRAPH_FORMATTING
27
24
  };
28
25
  /**
29
26
  * StyleResolver provides efficient access to resolved style properties
@@ -213,8 +210,8 @@ var StyleResolver = class {
213
210
  return this.stylesById.has(styleId);
214
211
  }
215
212
  findDefaultStyle(type) {
213
+ if (type === "paragraph") return resolveDefaultParagraphStyle(this.stylesById.values()) ?? (this.docDefaults ? void 0 : BUILTIN_NORMAL_STYLE);
216
214
  for (const style of this.stylesById.values()) if (style.type === type && style.default) return style;
217
- if (type === "paragraph") return this.stylesById.get("Normal") ?? (this.docDefaults ? void 0 : BUILTIN_NORMAL_STYLE);
218
215
  }
219
216
  };
220
217
  /**
@@ -1,9 +1,29 @@
1
1
  import { EditorView } from "prosemirror-view";
2
2
  //#region src/prosemirror/utils/visualLineNavigation.d.ts
3
+ /**
4
+ * Where the previous vertical step left the caret: the column later steps keep
5
+ * (`stickyX`), the visual line it settled on, and the caret position both
6
+ * describe. The line index is not redundant with the position — a soft-wrap
7
+ * boundary position belongs to two visual lines (the end of the wrapped line
8
+ * and the start of the next), so the painted DOM alone cannot say which one the
9
+ * caret sits on.
10
+ *
11
+ * @internal
12
+ */
13
+ type VisualLineStep = {
14
+ stickyX: number;
15
+ lineIndex: number;
16
+ pmPos: number;
17
+ };
3
18
  /** @internal */
4
19
  type VisualLineState = {
5
- stickyX: number | null;
6
- lastVisualLineIndex: number;
20
+ /**
21
+ * Only describes the caret while it is still at `step.pmPos`. Any other move
22
+ * (a click, a find result, an agent edit, typing) leaves the caret elsewhere,
23
+ * and the next vertical step re-resolves both column and line from the DOM
24
+ * instead of continuing from a line the caret has left.
25
+ */
26
+ step: VisualLineStep | null;
7
27
  };
8
28
  /** @internal */
9
29
  declare function createVisualLineState(): VisualLineState;