wordinweb 0.1.8 → 0.1.9

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.
package/dist/index.js CHANGED
@@ -6111,23 +6111,49 @@ var DocxDocument = class _DocxDocument {
6111
6111
  }
6112
6112
  this._modelVersion++;
6113
6113
  }
6114
- /**
6115
- * Reparse the two direct body paragraphs created by Enter without rebuilding
6116
- * the complete document model. This deliberately covers only the simple
6117
- * single-section body case used by incremental pagination; structural
6118
- * containers, revisions, bookmarks, and section breaks use refresh().
6119
- */
6114
+ /** Reparse the two sibling body-story paragraphs created by Enter without
6115
+ * rebuilding the complete document model. Paragraphs nested in table cells
6116
+ * are included because legal documents spend most of their body inside
6117
+ * tables; revisions, bookmarks, fields, and section breaks use refresh(). */
6120
6118
  reparseDirectBodyParagraphSplit(beforeSource, afterSource) {
6121
- if (this.sections.length !== 1) return null;
6122
- const body = child(this.docRoot, "body");
6123
- if (!body) return null;
6124
- const beforeIndex = body.children.indexOf(beforeSource);
6125
- if (beforeIndex < 0 || body.children[beforeIndex + 1] !== afterSource) return null;
6126
- if (localName(beforeSource.name) !== "p" || localName(afterSource.name) !== "p") return null;
6127
- const blocks = this.sections[0].blocks;
6128
- if (blocks.some((block) => block.src === afterSource)) return null;
6129
- const blockIndex = blocks.findIndex((block) => block.type === "paragraph" && block.src === beforeSource);
6130
- if (blockIndex < 0) return null;
6119
+ const parsed = this.reparseDirectBodyParagraphSplits(beforeSource, [afterSource]);
6120
+ return parsed ? { before: parsed[0], after: parsed[1] } : null;
6121
+ }
6122
+ /** Reparse a body paragraph plus several new siblings created by
6123
+ * click-and-type without rebuilding the complete document model. */
6124
+ reparseDirectBodyParagraphSplits(beforeSource, afterSources) {
6125
+ if (afterSources.length === 0) return null;
6126
+ const parent = this.findParentOf(beforeSource);
6127
+ if (!parent) return null;
6128
+ const beforeIndex = parent.children.indexOf(beforeSource);
6129
+ if (beforeIndex < 0 || afterSources.some((source, i) => parent.children[beforeIndex + i + 1] !== source)) {
6130
+ return null;
6131
+ }
6132
+ if (localName(beforeSource.name) !== "p" || afterSources.some((source) => localName(source.name) !== "p")) {
6133
+ return null;
6134
+ }
6135
+ const findBlockList = (blocks2) => {
6136
+ const index = blocks2.findIndex((block) => block.type === "paragraph" && block.src === beforeSource);
6137
+ if (index >= 0) return { blocks: blocks2, index };
6138
+ for (const block of blocks2) {
6139
+ if (block.type !== "table") continue;
6140
+ for (const row of block.rows) {
6141
+ for (const cell of row.cells) {
6142
+ const found = findBlockList(cell.blocks);
6143
+ if (found) return found;
6144
+ }
6145
+ }
6146
+ }
6147
+ return null;
6148
+ };
6149
+ let location = null;
6150
+ for (const section of this.sections) {
6151
+ location = findBlockList(section.blocks);
6152
+ if (location) break;
6153
+ }
6154
+ if (!location) return null;
6155
+ const { blocks, index: blockIndex } = location;
6156
+ if (afterSources.some((source) => blocks.some((block) => block.src === source))) return null;
6131
6157
  const old = blocks[blockIndex];
6132
6158
  if (old.type !== "paragraph" || old.sectionBreak) return null;
6133
6159
  const unsafe = (element) => {
@@ -6135,7 +6161,7 @@ var DocxDocument = class _DocxDocument {
6135
6161
  if (name === "sectPr" || name === "bookmarkStart" || name === "bookmarkEnd" || name === "fldChar" || name === "instrText" || name === "fldSimple" || name === "sdt" || name === "ins" || name === "del" || name.startsWith("move") || name.endsWith("PrChange")) return true;
6136
6162
  return element.children.some(unsafe);
6137
6163
  };
6138
- if (unsafe(beforeSource) || unsafe(afterSource)) return null;
6164
+ if (unsafe(beforeSource) || afterSources.some(unsafe)) return null;
6139
6165
  const readPart = (part) => this.readXmlOptional(part);
6140
6166
  const ctx = {
6141
6167
  ...this.ctxBase,
@@ -6143,11 +6169,103 @@ var DocxDocument = class _DocxDocument {
6143
6169
  readPart,
6144
6170
  independentTextboxStories: true
6145
6171
  };
6146
- const before = parseParagraph(beforeSource, ctx);
6147
- const after = parseParagraph(afterSource, ctx);
6148
- if (before.revisionHidden || after.revisionHidden || before.sectionBreak || after.sectionBreak) return null;
6149
- blocks.splice(blockIndex, 1, before, after);
6150
- return { before, after };
6172
+ const parsed = [beforeSource, ...afterSources].map((source) => parseParagraph(source, ctx));
6173
+ if (parsed.some((paragraph) => paragraph.revisionHidden || paragraph.sectionBreak)) return null;
6174
+ blocks.splice(blockIndex, 1, ...parsed);
6175
+ return parsed;
6176
+ }
6177
+ /** Reparse one retained body-story paragraph after a structural edit that
6178
+ * leaves its surrounding block list unchanged, such as inserting ink. */
6179
+ reparseBodyParagraph(source) {
6180
+ if (localName(source.name) !== "p") return null;
6181
+ const findBlockList = (blocks) => {
6182
+ const index = blocks.findIndex((block) => block.type === "paragraph" && block.src === source);
6183
+ if (index >= 0) return { blocks, index };
6184
+ for (const block of blocks) {
6185
+ if (block.type !== "table") continue;
6186
+ for (const row of block.rows) {
6187
+ for (const cell of row.cells) {
6188
+ const found = findBlockList(cell.blocks);
6189
+ if (found) return found;
6190
+ }
6191
+ }
6192
+ }
6193
+ return null;
6194
+ };
6195
+ let location = null;
6196
+ for (const section of this.sections) {
6197
+ location = findBlockList(section.blocks);
6198
+ if (location) break;
6199
+ }
6200
+ if (!location) return null;
6201
+ const old = location.blocks[location.index];
6202
+ if (old.type !== "paragraph" || old.sectionBreak) return null;
6203
+ const unsafe = (element) => {
6204
+ const name = localName(element.name);
6205
+ if (name === "sectPr" || name === "bookmarkStart" || name === "bookmarkEnd") return true;
6206
+ return element.children.some(unsafe);
6207
+ };
6208
+ if (unsafe(source)) return null;
6209
+ const paragraph = parseParagraph(source, {
6210
+ ...this.ctxBase,
6211
+ rels: this.documentRels,
6212
+ readPart: (part) => this.readXmlOptional(part),
6213
+ independentTextboxStories: true
6214
+ });
6215
+ if (paragraph.revisionHidden || paragraph.sectionBreak) return null;
6216
+ location.blocks[location.index] = paragraph;
6217
+ return paragraph;
6218
+ }
6219
+ /** Reparse two sibling body-story paragraphs after Backspace/Delete merged
6220
+ * their XML into one. Keeps the parsed model generation stable so long
6221
+ * documents can use incremental layout instead of repaginating in full. */
6222
+ reparseDirectBodyParagraphMerge(beforeSource, afterSource, survivorSource) {
6223
+ if (survivorSource !== beforeSource && survivorSource !== afterSource) return null;
6224
+ if (localName(beforeSource.name) !== "p" || localName(afterSource.name) !== "p") return null;
6225
+ const parent = this.findParentOf(survivorSource);
6226
+ if (!parent || parent.children.includes(survivorSource === beforeSource ? afterSource : beforeSource)) return null;
6227
+ const findBlockList = (blocks2) => {
6228
+ const index2 = blocks2.findIndex(
6229
+ (block, i) => block.type === "paragraph" && block.src === beforeSource && blocks2[i + 1]?.type === "paragraph" && blocks2[i + 1].src === afterSource
6230
+ );
6231
+ if (index2 >= 0) return { blocks: blocks2, index: index2 };
6232
+ for (const block of blocks2) {
6233
+ if (block.type !== "table") continue;
6234
+ for (const row of block.rows) {
6235
+ for (const cell of row.cells) {
6236
+ const found = findBlockList(cell.blocks);
6237
+ if (found) return found;
6238
+ }
6239
+ }
6240
+ }
6241
+ return null;
6242
+ };
6243
+ let location = null;
6244
+ for (const section of this.sections) {
6245
+ location = findBlockList(section.blocks);
6246
+ if (location) break;
6247
+ }
6248
+ if (!location) return null;
6249
+ const { blocks, index } = location;
6250
+ const before = blocks[index];
6251
+ const after = blocks[index + 1];
6252
+ if (before.type !== "paragraph" || after.type !== "paragraph" || before.sectionBreak || after.sectionBreak) return null;
6253
+ const unsafe = (element) => {
6254
+ const name = localName(element.name);
6255
+ if (name === "sectPr" || name === "bookmarkStart" || name === "bookmarkEnd" || name === "fldChar" || name === "instrText" || name === "fldSimple" || name === "sdt" || name === "ins" || name === "del" || name.startsWith("move") || name.endsWith("PrChange")) return true;
6256
+ return element.children.some(unsafe);
6257
+ };
6258
+ if (unsafe(beforeSource) || unsafe(afterSource)) return null;
6259
+ const readPart = (part) => this.readXmlOptional(part);
6260
+ const merged = parseParagraph(survivorSource, {
6261
+ ...this.ctxBase,
6262
+ rels: this.documentRels,
6263
+ readPart,
6264
+ independentTextboxStories: true
6265
+ });
6266
+ if (merged.revisionHidden || merged.sectionBreak) return null;
6267
+ blocks.splice(index, 2, merged);
6268
+ return merged;
6151
6269
  }
6152
6270
  deriveComments() {
6153
6271
  const out = [];
@@ -10833,6 +10951,7 @@ var __incrStats = {
10833
10951
  resumePage: -1,
10834
10952
  convergedBlock: -1,
10835
10953
  convergedPage: -1,
10954
+ pageShift: 0,
10836
10955
  blocksLaid: 0,
10837
10956
  fallbackReason: ""
10838
10957
  };
@@ -10933,6 +11052,7 @@ var Engine = class {
10933
11052
  this.trailingSectionBreakMarkGap = 0;
10934
11053
  /** Bookmark name -> formatted display page number (PAGEREF rewrite). */
10935
11054
  this.bookmarkPages = /* @__PURE__ */ new Map();
11055
+ this.bookmarkPageIndices = /* @__PURE__ */ new Map();
10936
11056
  /** STYLEREF page-awareness: styleIds whose paragraphs a header/footer STYLEREF
10937
11057
  * references (null until precomputed; empty set = no STYLEREF, tracking off). */
10938
11058
  this.styleRefTrack = null;
@@ -11025,6 +11145,8 @@ var Engine = class {
11025
11145
  this.incrConvergeBlockIdx = -1;
11026
11146
  this.incrConvergePageIdx = -1;
11027
11147
  this.incrConvergeItemDelta = 0;
11148
+ this.incrConvergePrevPointPageIdx = -1;
11149
+ this.incrPageShift = 0;
11028
11150
  /** New block indices are old indices plus this value after a structural
11029
11151
  * split, so convergence compares the same semantic suffix boundary. */
11030
11152
  this.incrBlockDelta = 0;
@@ -11231,6 +11353,7 @@ var Engine = class {
11231
11353
  points: this.incrPoints,
11232
11354
  pages: this.pages,
11233
11355
  bookmarks: this.bookmarkPages,
11356
+ bookmarkPageIndices: this.bookmarkPageIndices,
11234
11357
  modelVersion: this.doc.modelVersion,
11235
11358
  seqCounters: this.seqCounters,
11236
11359
  seqAssigned: this.seqAssigned,
@@ -11367,9 +11490,13 @@ var Engine = class {
11367
11490
  });
11368
11491
  if (this.incrPrevPoints && blockIdx > this.incrFirstDirty) {
11369
11492
  const pp = this.incrPrevPoints.get(blockIdx);
11370
- if (pp && pp.pageCount === globalPageIdx && this.statesMatch(pp.state)) {
11493
+ const pageShift = pp ? globalPageIdx - pp.pageCount : 0;
11494
+ if (pp && this.statesMatch(pp.state, pageShift)) {
11371
11495
  __incrStats.convergedBlock = blockIdx;
11372
11496
  __incrStats.convergedPage = globalPageIdx;
11497
+ __incrStats.pageShift = pageShift;
11498
+ this.incrPageShift = pageShift;
11499
+ this.incrConvergePrevPointPageIdx = pp.pageCount;
11373
11500
  this.incrPoints.push(snapshot());
11374
11501
  const reuseWholePage = p.items.length === 0 && pp.pageItemCount === 0 && Math.abs(this.y - p.bodyTop) < 1e-6;
11375
11502
  if (reuseWholePage) {
@@ -11394,9 +11521,9 @@ var Engine = class {
11394
11521
  /** True when the current engine state at a clean page top exactly matches a
11395
11522
  * prev-layout capture point — i.e. the relaid tail has re-converged and the
11396
11523
  * rest of the layout is guaranteed identical. */
11397
- statesMatch(s) {
11524
+ statesMatch(s, pageShift = 0) {
11398
11525
  const p = this.cur;
11399
- if (this.col !== s.col || this.y !== s.y || this.sectionFirstPagePhys !== s.sectionFirstPagePhys || this.lastParaSpacingAfter !== s.lastParaSpacingAfter || this.lastParaAfterPad !== s.lastParaAfterPad || this.lastParaWasEmpty !== s.lastParaWasEmpty || this.trailingSectionBreakMarkGap !== s.trailingSectionBreakMarkGap || this.suppressNextSpaceBefore !== s.suppressNextSpaceBefore || this.docGridDropBefore !== s.docGridDropBefore || this.gridResyncPending !== s.gridResyncPending || this.verticalGridFlow !== s.verticalGridFlow || this.bannerSlotUsed !== s.bannerSlotUsed || p.physIndex !== s.page.physIndex || p.displayNumber !== s.page.displayNumber || p.bodyTop !== s.page.bodyTop || p.bodyBottom !== s.page.bodyBottom) {
11526
+ if (this.col !== s.col || this.y !== s.y || this.sectionFirstPagePhys !== s.sectionFirstPagePhys || this.lastParaSpacingAfter !== s.lastParaSpacingAfter || this.lastParaAfterPad !== s.lastParaAfterPad || this.lastParaWasEmpty !== s.lastParaWasEmpty || this.trailingSectionBreakMarkGap !== s.trailingSectionBreakMarkGap || this.suppressNextSpaceBefore !== s.suppressNextSpaceBefore || this.docGridDropBefore !== s.docGridDropBefore || this.gridResyncPending !== s.gridResyncPending || this.verticalGridFlow !== s.verticalGridFlow || this.bannerSlotUsed !== s.bannerSlotUsed || p.physIndex !== s.page.physIndex + pageShift || p.displayNumber !== s.page.displayNumber + pageShift || p.bodyTop !== s.page.bodyTop || p.bodyBottom !== s.page.bodyBottom) {
11400
11527
  return false;
11401
11528
  }
11402
11529
  if (this.seenNumIds.size !== s.seenNumIds.length) return false;
@@ -11453,11 +11580,60 @@ var Engine = class {
11453
11580
  this.counters = new Map(s.counters.map(([k, v]) => [k, [...v]]));
11454
11581
  this.seenNumIds = new Set(s.seenNumIds);
11455
11582
  }
11583
+ bodyHasPageField(el16 = this.doc.docRoot) {
11584
+ const name = el16.name.includes(":") ? el16.name.slice(el16.name.indexOf(":") + 1) : el16.name;
11585
+ const instruction = name === "instrText" ? el16.text : name === "fldSimple" ? el16.attrs["w:instr"] ?? "" : "";
11586
+ if (/\bPAGE\b/i.test(instruction)) return true;
11587
+ return el16.children.some((child4) => this.bodyHasPageField(child4));
11588
+ }
11589
+ /** Clone a retained suffix page onto its new physical/display number. Body
11590
+ * items and page-border items stay identical; headers and footers are removed
11591
+ * so the final pass can rebuild PAGE fields against the shifted number. */
11592
+ shiftRetainedPage(page, pageShift) {
11593
+ if (page.hfStart === void 0) return null;
11594
+ const physIndex = page.physIndex + pageShift;
11595
+ const displayNumber = page.displayNumber + pageShift;
11596
+ const isFirstOfSection = physIndex === this.sectionFirstPagePhys;
11597
+ const useEven = this.doc.evenAndOddHeaders && displayNumber % 2 === 0;
11598
+ const headerRel = page.sp.titlePage && isFirstOfSection ? page.sp.headerRefs.first : useEven ? page.sp.headerRefs.even : page.sp.headerRefs.default;
11599
+ const footerRel = page.sp.titlePage && isFirstOfSection ? page.sp.footerRefs.first : useEven ? page.sp.footerRefs.even : page.sp.footerRefs.default;
11600
+ const shifted = {
11601
+ ...page,
11602
+ items: page.items.slice(0, page.hfStart),
11603
+ physIndex,
11604
+ displayNumber,
11605
+ headerRel,
11606
+ footerRel,
11607
+ hfStart: void 0,
11608
+ colXs: [...page.colXs],
11609
+ colWidths: [...page.colWidths],
11610
+ footnotes: page.footnotes.map((note) => ({ ...note, items: [...note.items] })),
11611
+ footnoteH: [...page.footnoteH],
11612
+ bands: page.bands.map((band) => ({
11613
+ ...band,
11614
+ colXs: [...band.colXs],
11615
+ colWidths: [...band.colWidths],
11616
+ bottoms: [...band.bottoms]
11617
+ }))
11618
+ };
11619
+ if (headerRel === page.headerRel && footerRel === page.footerRel) return shifted;
11620
+ this.sp = shifted.sp;
11621
+ const contentWidth = shifted.sp.pageWidth - shifted.sp.marginLeft - shifted.sp.marginRight - shifted.sp.gutter;
11622
+ const header = this.doc.headers.get(headerRel ?? "");
11623
+ const footer = this.doc.footers.get(footerRel ?? "");
11624
+ const headerHeight = this.measureHeaderFooter(header, shifted, contentWidth, this.pageFieldFrameOverlay(header));
11625
+ const footerHeight = this.measureHeaderFooter(footer, shifted, contentWidth, this.pageFieldFrameOverlay(footer));
11626
+ if (headerHeight !== page.headerHeight || footerHeight !== page.footerHeight) return null;
11627
+ shifted.headerHeight = headerHeight;
11628
+ shifted.footerHeight = footerHeight;
11629
+ return shifted;
11630
+ }
11456
11631
  /** Attempt an incremental layout; return null (and leave a discarded, dirty
11457
11632
  * engine) if reuse can't be proven safe, so the caller does a full layout in a
11458
11633
  * fresh engine. */
11459
11634
  runIncremental(prev, dirtyHint, dirtySource) {
11460
11635
  __incrStats.fallbackReason = "";
11636
+ __incrStats.pageShift = 0;
11461
11637
  const fallback = (reason) => {
11462
11638
  __incrStats.fallbackReason = reason;
11463
11639
  const perf2 = globalThis.__dxwPerf;
@@ -11472,11 +11648,12 @@ var Engine = class {
11472
11648
  const blocks = this.doc.sections[0].blocks;
11473
11649
  let newSigs = null;
11474
11650
  let firstDirty = 0;
11475
- if (dirtyHint && blocks.length === inc.sigs.length + 1) {
11651
+ if (dirtyHint && blocks.length > inc.sigs.length) {
11652
+ const delta = blocks.length - inc.sigs.length;
11476
11653
  const inserted = blocks.findIndex((block) => block.src === dirtyHint);
11477
- const changed = inserted - 1;
11654
+ const changed = inserted - delta;
11478
11655
  if (changed >= 0) {
11479
- let hashed = 2;
11656
+ let hashed = delta + 1;
11480
11657
  let neighborsOk = true;
11481
11658
  if (changed > 0) {
11482
11659
  hashed++;
@@ -11484,21 +11661,47 @@ var Engine = class {
11484
11661
  }
11485
11662
  if (neighborsOk && inserted < blocks.length - 1) {
11486
11663
  hashed++;
11487
- neighborsOk = this.blockSig(blocks[inserted + 1]) === inc.sigs[inserted];
11664
+ neighborsOk = this.blockSig(blocks[inserted + 1]) === inc.sigs[changed + 1];
11488
11665
  }
11489
11666
  if (neighborsOk) {
11490
11667
  newSigs = inc.sigs.slice();
11491
11668
  newSigs[changed] = this.blockSig(blocks[changed]);
11492
- newSigs.splice(inserted, 0, this.blockSig(blocks[inserted]));
11669
+ newSigs.splice(changed + 1, 0, ...blocks.slice(changed + 1, inserted + 1).map((block) => this.blockSig(block)));
11493
11670
  firstDirty = changed;
11494
11671
  __incrStats.hintFastPath = true;
11495
11672
  __incrStats.blocksHashed = hashed;
11496
11673
  __incrStats.firstDirty = changed;
11497
- this.incrBlockDelta = 1;
11674
+ this.incrBlockDelta = delta;
11498
11675
  this.incrBlockShiftAfter = changed;
11499
11676
  }
11500
11677
  }
11501
11678
  }
11679
+ if (newSigs === null && dirtyHint && blocks.length === inc.sigs.length - 1) {
11680
+ const changed = blocks.findIndex((block) => block.src === dirtyHint);
11681
+ if (changed >= 0) {
11682
+ let hashed = 1;
11683
+ let neighborsOk = true;
11684
+ if (changed > 0) {
11685
+ hashed++;
11686
+ neighborsOk = this.blockSig(blocks[changed - 1]) === inc.sigs[changed - 1];
11687
+ }
11688
+ if (neighborsOk && changed < blocks.length - 1) {
11689
+ hashed++;
11690
+ neighborsOk = this.blockSig(blocks[changed + 1]) === inc.sigs[changed + 2];
11691
+ }
11692
+ if (neighborsOk) {
11693
+ newSigs = inc.sigs.slice();
11694
+ newSigs[changed] = this.blockSig(blocks[changed]);
11695
+ newSigs.splice(changed + 1, 1);
11696
+ firstDirty = changed;
11697
+ __incrStats.hintFastPath = true;
11698
+ __incrStats.blocksHashed = hashed;
11699
+ __incrStats.firstDirty = changed;
11700
+ this.incrBlockDelta = -1;
11701
+ this.incrBlockShiftAfter = changed + 1;
11702
+ }
11703
+ }
11704
+ }
11502
11705
  if (newSigs === null && dirtyHint && blocks.length === inc.sigs.length) {
11503
11706
  const d = blocks.findIndex((b) => b.src === dirtyHint);
11504
11707
  if (d >= 0) {
@@ -11555,19 +11758,47 @@ var Engine = class {
11555
11758
  __incrStats.resumePage = rp.pageCount;
11556
11759
  __incrStats.convergedBlock = -1;
11557
11760
  __incrStats.convergedPage = -1;
11761
+ __incrStats.pageShift = 0;
11558
11762
  __incrStats.blocksLaid = 0;
11559
11763
  this.restoreIncrState(rp.state, inc.pages[rp.pageCount].items.slice(0, rp.pageItemCount));
11560
11764
  this.layoutBlocks(blocks, rp.blockIdx);
11561
11765
  if (this.incrAbort) return fallback("layout-abort");
11562
11766
  const suffixStart = this.incrConvergePrevPageIdx >= 0 ? this.incrConvergePrevPageIdx : inc.pages.length;
11563
- const totalPages = prefixCount + this.pages.length + (inc.pages.length - suffixStart);
11564
- if (totalPages !== prev.totalPages) return fallback("page-count");
11767
+ const middleCount = this.pages.length;
11768
+ const hasRetainedSuffix = suffixStart < inc.pages.length;
11769
+ const pageShift = hasRetainedSuffix ? prefixCount + middleCount - suffixStart : 0;
11770
+ if (hasRetainedSuffix && pageShift !== this.incrPageShift) return fallback("page-shift");
11771
+ if (pageShift !== 0 && this.bodyHasPageField()) return fallback("body-page-field-shift");
11772
+ const shiftedSuffix = [];
11773
+ if (pageShift !== 0) {
11774
+ for (const page of inc.pages.slice(suffixStart)) {
11775
+ const shifted = this.shiftRetainedPage(page, pageShift);
11776
+ if (!shifted) return fallback("shifted-header-footer");
11777
+ shiftedSuffix.push(shifted);
11778
+ }
11779
+ }
11565
11780
  const mergedBookmarks = new Map(inc.bookmarks);
11566
- const movedBookmarks = /* @__PURE__ */ new Set();
11781
+ const mergedBookmarkPageIndices = new Map(inc.bookmarkPageIndices);
11782
+ if (pageShift !== 0) {
11783
+ for (const [name, oldPageIndex] of inc.bookmarkPageIndices) {
11784
+ if (oldPageIndex < suffixStart) continue;
11785
+ const page = inc.pages[oldPageIndex];
11786
+ if (!page) continue;
11787
+ mergedBookmarkPageIndices.set(name, oldPageIndex + pageShift);
11788
+ mergedBookmarks.set(
11789
+ name,
11790
+ formatNumber(page.displayNumber + pageShift, PAGE_FMT[page.sp.pageNumberFormat ?? "decimal"] ?? "decimal")
11791
+ );
11792
+ }
11793
+ }
11567
11794
  for (const [name, page] of this.bookmarkPages) {
11568
- if (inc.bookmarks.get(name) !== page) movedBookmarks.add(name);
11569
11795
  mergedBookmarks.set(name, page);
11570
11796
  }
11797
+ for (const [name, pageIndex] of this.bookmarkPageIndices) mergedBookmarkPageIndices.set(name, pageIndex);
11798
+ const movedBookmarks = /* @__PURE__ */ new Set();
11799
+ for (const [name, page] of mergedBookmarks) {
11800
+ if (inc.bookmarks.get(name) !== page) movedBookmarks.add(name);
11801
+ }
11571
11802
  const pageRefUpdates = [];
11572
11803
  if (movedBookmarks.size > 0) {
11573
11804
  for (const page of inc.pages) {
@@ -11585,32 +11816,53 @@ var Engine = class {
11585
11816
  this.placeEndnotes();
11586
11817
  this.emitFootnoteAreas();
11587
11818
  this.emitColumnSeparators();
11588
- this.finalizeHeadersFooters();
11589
11819
  this.bookmarkPages = mergedBookmarks;
11590
- this.rewritePageRefs(this.pages);
11820
+ this.bookmarkPageIndices = mergedBookmarkPageIndices;
11591
11821
  this.applySectionVAlign();
11822
+ if (pageShift !== 0) {
11823
+ for (let i = 0; i < middleCount; i++) this.emitPageBorders(this.pages[i]);
11824
+ this.pages.push(...shiftedSuffix);
11825
+ this.finalizeHeadersFooters(false);
11826
+ } else {
11827
+ this.finalizeHeadersFooters();
11828
+ }
11829
+ this.rewritePageRefs(this.pages);
11592
11830
  if (this.incrAbort) return fallback("postpass-abort");
11593
- const middle = this.pages.map((p) => laidOutPage(p));
11831
+ const middle = this.pages.slice(0, middleCount).map((p) => laidOutPage(p));
11594
11832
  let structuralPrefixEnd = prefixCount;
11595
11833
  if (dirtySource) {
11596
- const dirtyMiddleIdx = this.pages.findIndex(
11834
+ const dirtyMiddleIdx = this.pages.slice(0, middleCount).findIndex(
11597
11835
  (page) => page.items.some((item) => item.kind === "text" && item.src?.t === dirtySource)
11598
11836
  );
11599
11837
  if (dirtyMiddleIdx > 0) structuralPrefixEnd += dirtyMiddleIdx;
11600
11838
  }
11601
- const outPages = prev.pages.slice(0, prefixCount).concat(middle, prev.pages.slice(suffixStart));
11602
- const outInternal = inc.pages.slice(0, prefixCount).concat(this.pages, inc.pages.slice(suffixStart));
11603
- const samePageTail = this.incrConvergeBlockIdx >= 0 ? inc.points.filter(
11604
- (pt) => pt.pageCount === this.incrConvergePageIdx && shiftedBlockIdx(pt.blockIdx) > this.incrConvergeBlockIdx
11605
- ).map((pt) => ({
11839
+ const outPages = prev.pages.slice(0, prefixCount).concat(
11840
+ pageShift !== 0 ? this.pages.map((page) => laidOutPage(page)) : middle.concat(prev.pages.slice(suffixStart))
11841
+ );
11842
+ const outInternal = inc.pages.slice(0, prefixCount).concat(
11843
+ pageShift !== 0 ? this.pages : this.pages.concat(inc.pages.slice(suffixStart))
11844
+ );
11845
+ const shiftPoint = (pt, itemDelta = 0) => ({
11606
11846
  ...pt,
11607
11847
  blockIdx: shiftedBlockIdx(pt.blockIdx),
11608
- pageItemCount: pt.pageItemCount + this.incrConvergeItemDelta
11609
- })) : [];
11848
+ pageCount: pt.pageCount + pageShift,
11849
+ pageItemCount: pt.pageItemCount + itemDelta,
11850
+ state: {
11851
+ ...pt.state,
11852
+ page: {
11853
+ ...pt.state.page,
11854
+ physIndex: pt.state.page.physIndex + pageShift,
11855
+ displayNumber: pt.state.page.displayNumber + pageShift
11856
+ }
11857
+ }
11858
+ });
11859
+ const samePageTail = this.incrConvergeBlockIdx >= 0 ? inc.points.filter(
11860
+ (pt) => pt.pageCount === this.incrConvergePrevPointPageIdx && shiftedBlockIdx(pt.blockIdx) > this.incrConvergeBlockIdx
11861
+ ).map((pt) => shiftPoint(pt, this.incrConvergeItemDelta)) : [];
11610
11862
  const points = inc.points.filter((pt) => pt.pageCount < prefixCount).concat(
11611
11863
  this.incrPoints,
11612
11864
  samePageTail,
11613
- inc.points.filter((pt) => pt.pageCount >= suffixStart).map((pt) => ({ ...pt, blockIdx: shiftedBlockIdx(pt.blockIdx) }))
11865
+ inc.points.filter((pt) => pt.pageCount >= suffixStart).map((pt) => shiftPoint(pt))
11614
11866
  );
11615
11867
  const perf = globalThis.__dxwPerf;
11616
11868
  if (perf) perf.incr = { ...__incrStats };
@@ -11623,6 +11875,7 @@ var Engine = class {
11623
11875
  points,
11624
11876
  pages: outInternal,
11625
11877
  bookmarks: mergedBookmarks,
11878
+ bookmarkPageIndices: mergedBookmarkPageIndices,
11626
11879
  modelVersion: this.doc.modelVersion,
11627
11880
  seqCounters: this.seqCounters,
11628
11881
  seqAssigned: this.seqAssigned,
@@ -11964,6 +12217,7 @@ var Engine = class {
11964
12217
  counters: new Map(Array.from(this.counters, ([k, v]) => [k, [...v]])),
11965
12218
  seenNumIds: new Set(this.seenNumIds),
11966
12219
  bookmarkPages: new Map(this.bookmarkPages),
12220
+ bookmarkPageIndices: new Map(this.bookmarkPageIndices),
11967
12221
  placedFootnotes: new Set(this.placedFootnotes),
11968
12222
  lnCounter: this.lnCounter,
11969
12223
  lnLastPage: this.lnLastPage,
@@ -12003,6 +12257,7 @@ var Engine = class {
12003
12257
  this.counters = s.counters;
12004
12258
  this.seenNumIds = s.seenNumIds;
12005
12259
  this.bookmarkPages = s.bookmarkPages;
12260
+ this.bookmarkPageIndices = s.bookmarkPageIndices;
12006
12261
  this.placedFootnotes = s.placedFootnotes;
12007
12262
  this.lnCounter = s.lnCounter;
12008
12263
  this.lnLastPage = s.lnLastPage;
@@ -13523,6 +13778,7 @@ var Engine = class {
13523
13778
  for (const bm of para.bookmarks) {
13524
13779
  if (!this.bookmarkPages.has(bm)) {
13525
13780
  this.bookmarkPages.set(bm, formatNumber(pg.displayNumber, PAGE_FMT[pg.sp.pageNumberFormat ?? "decimal"] ?? "decimal"));
13781
+ this.bookmarkPageIndices.set(bm, pg.physIndex - 1);
13526
13782
  }
13527
13783
  }
13528
13784
  }
@@ -16905,7 +17161,7 @@ function renderToDom(doc, layout, container, options = {}, prev) {
16905
17161
  renderComments(doc, root, handle.bindings, zoom, options.onDeleteComment, options.onReplyComment);
16906
17162
  }
16907
17163
  };
16908
- const wantedPages = () => {
17164
+ const wantedPages = (overscan = 2) => {
16909
17165
  const wanted = /* @__PURE__ */ new Set();
16910
17166
  const top = container.scrollTop;
16911
17167
  const bottom = top + Math.max(container.clientHeight, 1);
@@ -16921,15 +17177,15 @@ function renderToDom(doc, layout, container, options = {}, prev) {
16921
17177
  y = pageBottom + gap;
16922
17178
  }
16923
17179
  if (first < 0) first = last = Math.max(0, pages.length - 1);
16924
- for (let i = Math.max(0, first - 2); i <= Math.min(pages.length - 1, last + 2); i++) wanted.add(i);
17180
+ for (let i = Math.max(0, first - overscan); i <= Math.min(pages.length - 1, last + overscan); i++) wanted.add(i);
16925
17181
  for (let i = 0; i < pages.length; i++) {
16926
17182
  if (pages[i].mounted && pages[i].el.querySelector("[data-dxw-caret], .dxw-sel")) wanted.add(i);
16927
17183
  }
16928
17184
  return wanted;
16929
17185
  };
16930
- const syncViewport = (notify, force = false) => {
17186
+ const syncViewport = (notify, force = false, overscan = 2) => {
16931
17187
  if (!virtualized) return;
16932
- const wanted = wantedPages();
17188
+ const wanted = wantedPages(overscan);
16933
17189
  let changed = false;
16934
17190
  for (const i of wanted) {
16935
17191
  if (pages[i].mounted) continue;
@@ -16944,14 +17200,30 @@ function renderToDom(doc, layout, container, options = {}, prev) {
16944
17200
  }
16945
17201
  if (!changed && !force) return;
16946
17202
  refreshBindings();
16947
- redrawComments();
17203
+ if (drawComments || force) redrawComments();
16948
17204
  if (notify) options.onViewportChange?.();
16949
17205
  };
17206
+ let settleTimer;
17207
+ let syncedScrollTop = container.scrollTop;
17208
+ let pendingOverscan = 2;
16950
17209
  const scheduleViewport = () => {
17210
+ const fast = Math.abs(container.scrollTop - syncedScrollTop) > Math.max(container.clientHeight, 1) * 2;
17211
+ if (fast) {
17212
+ pendingOverscan = 0;
17213
+ if (settleTimer !== void 0) clearTimeout(settleTimer);
17214
+ settleTimer = setTimeout(() => {
17215
+ settleTimer = void 0;
17216
+ syncedScrollTop = container.scrollTop;
17217
+ syncViewport(true);
17218
+ }, 80);
17219
+ }
16951
17220
  if (frame) return;
16952
17221
  frame = requestAnimationFrame(() => {
16953
17222
  frame = 0;
16954
- syncViewport(true);
17223
+ const overscan = pendingOverscan;
17224
+ pendingOverscan = 2;
17225
+ syncedScrollTop = container.scrollTop;
17226
+ syncViewport(true, false, overscan);
16955
17227
  });
16956
17228
  };
16957
17229
  const stopVirtualizer = () => {
@@ -16960,6 +17232,8 @@ function renderToDom(doc, layout, container, options = {}, prev) {
16960
17232
  window.removeEventListener("resize", scheduleViewport);
16961
17233
  if (frame) cancelAnimationFrame(frame);
16962
17234
  frame = 0;
17235
+ if (settleTimer !== void 0) clearTimeout(settleTimer);
17236
+ settleTimer = void 0;
16963
17237
  };
16964
17238
  handle = {
16965
17239
  root,
@@ -18475,6 +18749,10 @@ function mergeParagraphBackward(doc, pEl) {
18475
18749
  if (idx <= 0) return false;
18476
18750
  const prev = parent.children[idx - 1];
18477
18751
  if (localName(prev.name) !== "p") return false;
18752
+ const finishMerge = (survivor) => {
18753
+ if (!doc.reparseDirectBodyParagraphMerge(prev, pEl, survivor)) doc.refresh();
18754
+ return true;
18755
+ };
18478
18756
  const visible = [];
18479
18757
  const collect = (el16) => {
18480
18758
  for (const c of el16.children) {
@@ -18492,6 +18770,7 @@ function mergeParagraphBackward(doc, pEl) {
18492
18770
  if (type === "page" || type === "column") {
18493
18771
  if (visible.length === 1) {
18494
18772
  parent.children.splice(idx - 1, 1);
18773
+ return finishMerge(pEl);
18495
18774
  } else {
18496
18775
  last.parent.children.splice(last.parent.children.indexOf(last.el), 1);
18497
18776
  }
@@ -18501,14 +18780,12 @@ function mergeParagraphBackward(doc, pEl) {
18501
18780
  }
18502
18781
  if (visible.length === 0) {
18503
18782
  parent.children.splice(idx - 1, 1);
18504
- doc.refresh();
18505
- return true;
18783
+ return finishMerge(pEl);
18506
18784
  }
18507
18785
  const moved = pEl.children.filter((c) => localName(c.name) !== "pPr");
18508
18786
  prev.children.push(...moved);
18509
18787
  parent.children.splice(idx, 1);
18510
- doc.refresh();
18511
- return true;
18788
+ return finishMerge(prev);
18512
18789
  }
18513
18790
  function firstTextOf(pEl) {
18514
18791
  const walk2 = (el16) => {
@@ -20538,7 +20815,7 @@ function setDrawingWordArtText(doc, drawing, text) {
20538
20815
  doc.refresh();
20539
20816
  return true;
20540
20817
  }
20541
- function insertInkAt(doc, caretT, points, color = "#202124", widthPx = 2, opacity = 1) {
20818
+ function insertInkAt(doc, caretT, points, color = "#202124", widthPx = 2, opacity = 1, refreshModel = true) {
20542
20819
  if (points.length < 2) return null;
20543
20820
  const caretRun = doc.findParentOf(caretT);
20544
20821
  const parent = caretRun && doc.findParentOf(caretRun);
@@ -20615,7 +20892,7 @@ function insertInkAt(doc, caretT, points, color = "#202124", widthPx = 2, opacit
20615
20892
  ])
20616
20893
  ]);
20617
20894
  parent.children.splice(parent.children.indexOf(caretRun) + 1, 0, el6(`${w}r`, {}, [drawing]));
20618
- doc.refresh();
20895
+ if (refreshModel) doc.refresh();
20619
20896
  return drawing;
20620
20897
  }
20621
20898
 
@@ -21651,7 +21928,11 @@ var _DocxEditor = class _DocxEditor {
21651
21928
  this.selection = null;
21652
21929
  this.selectionRects = [];
21653
21930
  this.selectedAll = false;
21654
- /** A click-and-type placement below a float has already recorded the undo
21931
+ /** Header/footer membership for hit-testing and caret movement. Rebuilt only
21932
+ * when refresh() replaces the parsed model; header/footer trees are tiny. */
21933
+ this.regionModelVersion = -1;
21934
+ this.headerFooterElements = /* @__PURE__ */ new WeakSet();
21935
+ /** A click-and-type placement in body whitespace has already recorded the undo
21655
21936
  * snapshot that the following text insertion belongs to. */
21656
21937
  this.pendingClickTypeCheckpoint = false;
21657
21938
  this.clickTypeCheckpointUntil = 0;
@@ -22009,7 +22290,7 @@ var _DocxEditor = class _DocxEditor {
22009
22290
  this.hfPage = null;
22010
22291
  }
22011
22292
  if (caret && region === "body" && !onGlyph && e.detail === 1 && !e.shiftKey) {
22012
- caret = this.extendCaretBelowFloatingTable(caret, e.clientX, e.clientY) ?? caret;
22293
+ caret = this.extendCaretBelowFloatingTable(caret, e.clientX, e.clientY) ?? this.extendCaretIntoBodyWhitespace(caret, e.clientX, e.clientY) ?? caret;
22013
22294
  }
22014
22295
  if (caret && e.shiftKey) {
22015
22296
  const anchor = this.anchorPoint();
@@ -22270,6 +22551,7 @@ var _DocxEditor = class _DocxEditor {
22270
22551
  /** Focus the hidden text target (keeps IME working); falls back to the
22271
22552
  * container if the textarea is not attached yet. */
22272
22553
  focusText() {
22554
+ if (!this.imeEl.isConnected) this.host.container.appendChild(this.imeEl);
22273
22555
  if (this.imeEl.isConnected) this.imeEl.focus({ preventScroll: true });
22274
22556
  else this.host.container.focus({ preventScroll: true });
22275
22557
  }
@@ -22278,6 +22560,14 @@ var _DocxEditor = class _DocxEditor {
22278
22560
  this.applyHfChrome();
22279
22561
  this.paintSelection();
22280
22562
  this.positionCaret();
22563
+ if (this.caret || this.selection) this.focusText();
22564
+ }
22565
+ /** Repaint only state affected by virtualized page mounts. Scrolling keeps
22566
+ * the current focus and skips header/footer mode notifications in body mode. */
22567
+ afterViewportChange() {
22568
+ if (this.inHeaderFooter) this.applyHfChrome();
22569
+ if (this.selection) this.paintSelection();
22570
+ if (this.caret) this.positionCaret();
22281
22571
  }
22282
22572
  // ---------- owned selection ----------
22283
22573
  hasSelection() {
@@ -23034,6 +23324,12 @@ var _DocxEditor = class _DocxEditor {
23034
23324
  const page = target.closest?.(".dxw-page");
23035
23325
  const surface = page?.firstElementChild;
23036
23326
  if (!page || !surface || !surface.contains(target)) return false;
23327
+ const zoom = this.host.zoom ?? 1;
23328
+ const rect = surface.getBoundingClientRect();
23329
+ const localY = (e.clientY - rect.top) / zoom;
23330
+ const bodyTop = parseFloat(page.dataset.bodyTop ?? "0");
23331
+ const bodyBottom = parseFloat(page.dataset.bodyBottom ?? "0");
23332
+ if (localY < bodyTop || localY > bodyBottom) return false;
23037
23333
  this.deselectInkGroup();
23038
23334
  if (tool.kind === "eraser") return this.startInkEraser(e, page, tool.size);
23039
23335
  if (tool.kind === "lasso") return this.startInkLasso(e, page, surface);
@@ -23043,8 +23339,6 @@ var _DocxEditor = class _DocxEditor {
23043
23339
  this.deselectImage();
23044
23340
  this.deselectWordArt();
23045
23341
  this.hideCaret();
23046
- const zoom = this.host.zoom ?? 1;
23047
- const rect = surface.getBoundingClientRect();
23048
23342
  const pointAt = (clientX, clientY) => ({
23049
23343
  x: Math.max(0, Math.min((clientX - rect.left) / zoom, rect.width / zoom)),
23050
23344
  y: Math.max(0, Math.min((clientY - rect.top) / zoom, rect.height / zoom))
@@ -23081,9 +23375,18 @@ var _DocxEditor = class _DocxEditor {
23081
23375
  if (points.length < 2) return;
23082
23376
  const anchor = this.nearestCaret(e.clientX, e.clientY, "body") ?? this.nearestCaret(up.clientX, up.clientY, "body");
23083
23377
  if (!anchor) return;
23378
+ const paragraph = paragraphOf(this.host.doc, anchor.t);
23379
+ const dirtyBlock = topLevelBlockOf(this.host.doc, anchor.t) ?? paragraph ?? void 0;
23084
23380
  this.host.history?.checkpoint();
23085
- if (insertInkAt(this.host.doc, anchor.t, points, tool.color, tool.width, tool.kind === "highlighter" ? 0.45 : 1)) {
23086
- this.host.rerender(void 0, "global");
23381
+ if (insertInkAt(this.host.doc, anchor.t, points, tool.color, tool.width, tool.kind === "highlighter" ? 0.45 : 1, false)) {
23382
+ const reparsed = paragraph ? this.host.doc.reparseBodyParagraph(paragraph) : null;
23383
+ if (reparsed) {
23384
+ invalidateParagraphSignature(paragraph);
23385
+ this.host.rerender(dirtyBlock, "local", anchor.t);
23386
+ } else {
23387
+ this.host.doc.refresh();
23388
+ this.host.rerender(void 0, "global");
23389
+ }
23087
23390
  }
23088
23391
  };
23089
23392
  document.addEventListener("mousemove", onMove);
@@ -24518,14 +24821,19 @@ var _DocxEditor = class _DocxEditor {
24518
24821
  return caretAt(t, 0);
24519
24822
  }
24520
24823
  regionOf(t) {
24521
- let cur = t;
24522
- let root;
24523
- while (cur) {
24524
- root = cur;
24525
- cur = this.host.doc.findParentOf(cur);
24824
+ if (this.regionModelVersion !== this.host.doc.modelVersion) {
24825
+ this.headerFooterElements = /* @__PURE__ */ new WeakSet();
24826
+ const visit = (element) => {
24827
+ this.headerFooterElements.add(element);
24828
+ for (const child4 of element.children) visit(child4);
24829
+ };
24830
+ for (const root of this.host.doc.revisionRoots()) {
24831
+ const name = localName(root.name);
24832
+ if (name === "hdr" || name === "ftr") visit(root);
24833
+ }
24834
+ this.regionModelVersion = this.host.doc.modelVersion;
24526
24835
  }
24527
- const ln = root ? localName(root.name) : "";
24528
- return ln === "hdr" || ln === "ftr" ? "hf" : "body";
24836
+ return this.headerFooterElements.has(t) ? "hf" : "body";
24529
24837
  }
24530
24838
  /**
24531
24839
  * Snap to the closest character boundary on the clicked line — makes
@@ -24689,6 +24997,97 @@ var _DocxEditor = class _DocxEditor {
24689
24997
  this.host.rerender(void 0, "global");
24690
24998
  return caret;
24691
24999
  }
25000
+ /** Word-style click-and-type: clicking below the final body line on a page
25001
+ * creates the intervening empty paragraphs so vertical arrows visit every
25002
+ * visible line instead of jumping across synthetic paragraph spacing. */
25003
+ extendCaretIntoBodyWhitespace(caret, x, y) {
25004
+ const handle = this.host.getHandle();
25005
+ const page = document.elementFromPoint(x, y)?.closest(".dxw-page");
25006
+ const surface = page?.firstElementChild;
25007
+ if (!handle || !page || !surface) return null;
25008
+ const rect = surface.getBoundingClientRect();
25009
+ const zoom = this.host.zoom ?? 1;
25010
+ const ly = (y - rect.top) / zoom;
25011
+ const bodyTop = parseFloat(page.dataset.bodyTop ?? "0");
25012
+ const bodyBottom = parseFloat(page.dataset.bodyBottom ?? "0");
25013
+ if (ly < bodyTop || ly > bodyBottom) return null;
25014
+ const paragraph = paragraphOf(this.host.doc, caret.t);
25015
+ const parent = paragraph ? this.host.doc.findParentOf(paragraph) : void 0;
25016
+ if (!paragraph || !parent || localName(parent.name) !== "body") return null;
25017
+ const endText = lastTextOf(paragraph);
25018
+ if (!endText) return null;
25019
+ const binding = (handle.bindingsByText.get(endText) ?? []).filter((candidate) => candidate.el.parentElement === surface && candidate.item.src).sort((a, b) => b.item.lineTop - a.item.lineTop)[0];
25020
+ if (!binding) return null;
25021
+ const lineHeight = Math.max(1, binding.item.lineHeight);
25022
+ if (ly <= binding.item.lineTop + lineHeight / 2) return null;
25023
+ const hasLaterLine = handle.bindings.some(
25024
+ (candidate) => candidate.el.parentElement === surface && candidate.item.src?.t && this.regionOf(candidate.item.src.t) === "body" && candidate.item.lineTop > binding.item.lineTop + lineHeight / 2
25025
+ );
25026
+ if (hasLaterLine) return null;
25027
+ const lineCount = Math.max(1, Math.round((ly - binding.item.lineTop) / lineHeight));
25028
+ this.host.history?.checkpoint();
25029
+ this.caret = {
25030
+ t: endText,
25031
+ run: binding.item.src.run,
25032
+ offset: endText.text.length,
25033
+ bias: endText.text.length > 0 ? "end" : void 0
25034
+ };
25035
+ const split = this.splitParagraphCore();
25036
+ if (!split) return null;
25037
+ const afterSources = [split.after];
25038
+ const w = split.after.name.includes(":") ? split.after.name.slice(0, split.after.name.indexOf(":") + 1) : "";
25039
+ const templatePPr = split.after.children.find((child4) => localName(child4.name) === "pPr");
25040
+ const templateRun = split.after.children.find(
25041
+ (child4) => localName(child4.name) === "r" && child4.children.includes(this.caret.t)
25042
+ );
25043
+ const templateRPr = templateRun?.children.find((child4) => localName(child4.name) === "rPr");
25044
+ let previous = split.after;
25045
+ for (let i = 1; i < lineCount; i++) {
25046
+ if (this.suggesting) markParagraphGlyph(previous, "ins", this.revMeta());
25047
+ const t = {
25048
+ name: this.caret.t.name,
25049
+ attrs: { ...this.caret.t.attrs, "xml:space": "preserve" },
25050
+ children: [],
25051
+ text: ""
25052
+ };
25053
+ const run = {
25054
+ name: templateRun?.name ?? `${w}r`,
25055
+ attrs: { ...templateRun?.attrs ?? {} },
25056
+ children: [...templateRPr ? [cloneXml(templateRPr)] : [], t],
25057
+ text: ""
25058
+ };
25059
+ const paragraph2 = {
25060
+ name: split.after.name,
25061
+ attrs: {},
25062
+ children: [...templatePPr ? [cloneXml(templatePPr)] : [], run],
25063
+ text: ""
25064
+ };
25065
+ const previousIndex = parent.children.indexOf(previous);
25066
+ parent.children.splice(previousIndex + 1, 0, paragraph2);
25067
+ afterSources.push(paragraph2);
25068
+ previous = paragraph2;
25069
+ this.caret = { t, run: caret.run, offset: 0 };
25070
+ }
25071
+ const reparsed = this.host.doc.reparseDirectBodyParagraphSplits(split.before, afterSources);
25072
+ if (reparsed) {
25073
+ for (const child4 of reparsed[reparsed.length - 1].children) {
25074
+ const runs = child4.type === "run" ? [child4] : child4.runs;
25075
+ const run = runs.find(
25076
+ (candidate) => candidate.content.some((content) => content.kind === "text" && content.srcT === this.caret.t)
25077
+ );
25078
+ if (run) {
25079
+ this.caret.run = run;
25080
+ break;
25081
+ }
25082
+ }
25083
+ invalidateParagraphSignature(split.before);
25084
+ for (const source of afterSources) invalidateParagraphSignature(source);
25085
+ }
25086
+ this.pendingClickTypeCheckpoint = true;
25087
+ this.commit(false, "local", !!reparsed);
25088
+ this.focusText();
25089
+ return this.caret;
25090
+ }
24692
25091
  caretFromPoint(x, y) {
24693
25092
  const doc = document;
24694
25093
  let node = null;
@@ -24991,7 +25390,8 @@ var _DocxEditor = class _DocxEditor {
24991
25390
  if (mergeParagraphBackward(this.host.doc, pEl)) {
24992
25391
  const target = junction && paragraphOf(this.host.doc, junction) ? junction : caret.t;
24993
25392
  this.caret = { t: target, run: caret.run, offset: target === junction ? target.text.length : 0 };
24994
- this.commit();
25393
+ invalidateParagraphSignature(paragraphOf(this.host.doc, target) ?? pEl);
25394
+ this.commit(false, "local", true);
24995
25395
  }
24996
25396
  return;
24997
25397
  }
@@ -25011,7 +25411,10 @@ var _DocxEditor = class _DocxEditor {
25011
25411
  const next = siblingParagraph(this.host.doc, pEl, 1);
25012
25412
  if (!next) return;
25013
25413
  this.host.history?.checkpoint();
25014
- if (mergeParagraphBackward(this.host.doc, next)) this.commit();
25414
+ if (mergeParagraphBackward(this.host.doc, next)) {
25415
+ invalidateParagraphSignature(pEl);
25416
+ this.commit(false, "local", true);
25417
+ }
25015
25418
  return;
25016
25419
  }
25017
25420
  if (!this.stepToNeighbor(1)) return;
@@ -25241,6 +25644,7 @@ var _DocxEditor = class _DocxEditor {
25241
25644
  invalidateParagraphSignature(split.after);
25242
25645
  }
25243
25646
  this.commit(false, "local", !!reparsed);
25647
+ this.focusText();
25244
25648
  }
25245
25649
  /** Split the paragraph at the caret without history or commit — shared by
25246
25650
  * Enter and multi-line paste. */
@@ -29822,7 +30226,7 @@ function DocxView({
29822
30226
  comments: showComments,
29823
30227
  onDeleteComment,
29824
30228
  onReplyComment,
29825
- onViewportChange: () => editor?.afterRender()
30229
+ onViewportChange: () => editor?.afterViewportChange()
29826
30230
  }, prev ?? void 0);
29827
30231
  container.scrollTop = scrollTop;
29828
30232
  container.scrollLeft = scrollLeft;