superdoc 2.4.0-next.42 → 2.4.0-next.44

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.
@@ -3701,6 +3701,7 @@ function createSuperDocUI(options) {
3701
3701
  if (options?.invalidateQueuedNavigation !== false) queuedTrackChangeNavigationInvalidation += 1;
3702
3702
  };
3703
3703
  const listeners = /* @__PURE__ */ new Set();
3704
+ let state;
3704
3705
  let disposed = false;
3705
3706
  let controllerRef = null;
3706
3707
  const asyncReads = /* @__PURE__ */ new Map();
@@ -6666,15 +6667,24 @@ function createSuperDocUI(options) {
6666
6667
  const target = rawTarget && typeof rawTarget === "object" ? rawTarget : null;
6667
6668
  let next = null;
6668
6669
  if (target?.entityType === "trackedChange" && typeof target.entityId === "string" && target.entityId.length > 0) {
6669
- const paintedEntityId = target.entityId;
6670
6670
  const targetStory = target.story && typeof target.story === "object" ? target.story : void 0;
6671
- const allStoryItems = readAllStoryTrackChanges();
6672
- const entityId = (allStoryItems ? targetStory ? buildStoryScopedTrackedChangeIdContext(allStoryItems, targetStory).toPublicId(paintedEntityId) : buildTrackedChangeIdContext(allStoryItems).toPublicId(paintedEntityId) : null) ?? paintedEntityId;
6673
6671
  const story = targetStory && storyLocatorSignature(targetStory) !== storyLocatorSignature(null) ? targetStory : void 0;
6672
+ const rawPaintedEntityId = typeof target.paintedEntityId === "string" && target.paintedEntityId.length > 0 ? target.paintedEntityId : null;
6673
+ const lookupEntityId = rawPaintedEntityId ?? target.entityId;
6674
+ const publicId = (() => {
6675
+ if (story) {
6676
+ const allStoryItems = readAllStoryTrackChanges();
6677
+ const storyContext = allStoryItems ? buildStoryScopedTrackedChangeIdContext(allStoryItems, story) : null;
6678
+ return storyContext?.toPublicId(lookupEntityId) ?? storyContext?.toPublicId(target.entityId) ?? target.entityId;
6679
+ }
6680
+ const bodyContext = buildTrackedChangeIdContext(Array.isArray(state?.trackChanges.items) ? state.trackChanges.items : []);
6681
+ return bodyContext.toPublicId(lookupEntityId) ?? bodyContext.toPublicId(target.entityId) ?? target.entityId;
6682
+ })();
6683
+ const paintedEntityId = rawPaintedEntityId ?? (lookupEntityId !== publicId ? lookupEntityId : null);
6674
6684
  next = {
6675
- id: entityId,
6685
+ id: publicId,
6676
6686
  ...story ? { story } : {},
6677
- ...typeof target.paintedEntityId === "string" && target.paintedEntityId.length > 0 ? { paintedEntityId: target.paintedEntityId } : entityId !== paintedEntityId ? { paintedEntityId } : {}
6687
+ ...paintedEntityId && paintedEntityId !== publicId ? { paintedEntityId } : {}
6678
6688
  };
6679
6689
  }
6680
6690
  if (rawTarget == null && next == null && explicitActiveChange != null && hasPendingTrackChangeRevealFocus(explicitActiveChange)) return false;
@@ -6740,7 +6750,7 @@ function createSuperDocUI(options) {
6740
6750
  };
6741
6751
  syncCoordinatorEditor();
6742
6752
  syncTrackedChangeFocusFromHostReviewTarget(readHostActiveReviewTarget(readHostReviewSource()));
6743
- let state = computeState();
6753
+ state = computeState();
6744
6754
  lastOptimisticInlineSelectionSignature = selectionInlineValueSignature(state.selection);
6745
6755
  const syncHostEventsSubscription = () => {
6746
6756
  const events = (getEditor()?.host)?.events;
@@ -8742,6 +8752,66 @@ function createSuperDocUI(options) {
8742
8752
  }
8743
8753
  };
8744
8754
  /**
8755
+ * Derive the browser-shell's `importedId` carrier-lookup alias from a
8756
+ * public list row's `wordRevisionIds` / `sourceIds` provenance fields,
8757
+ * mirroring `create-v2-tracked-changes-adapter.js`'s own derivation
8758
+ * (`wordRevisionIds.insert ?? .delete ?? .format ?? sourceIds.wordIdInsert
8759
+ * ?? .wordIdDelete ?? .wordIdOther[0]`). A carrier imported from a legacy
8760
+ * Word revision id is painted with THIS id, not the canonical public id —
8761
+ * `focusTrackedChange`'s carrier lookup only tries it when explicitly
8762
+ * passed as `importedId` on an object input (see `scrollTrackChangeIntoView`).
8763
+ */
8764
+ const deriveTrackedChangeImportedId = (row) => {
8765
+ if (!row || typeof row !== "object") return null;
8766
+ const record = row;
8767
+ const wordRevisionIds = record.wordRevisionIds && typeof record.wordRevisionIds === "object" ? record.wordRevisionIds : null;
8768
+ const sourceIds = record.sourceIds && typeof record.sourceIds === "object" ? record.sourceIds : null;
8769
+ const wordIdOther = Array.isArray(sourceIds?.wordIdOther) ? sourceIds.wordIdOther : void 0;
8770
+ const candidate = wordRevisionIds?.insert ?? wordRevisionIds?.delete ?? wordRevisionIds?.format ?? sourceIds?.wordIdInsert ?? sourceIds?.wordIdDelete ?? wordIdOther?.[0];
8771
+ return typeof candidate === "string" && candidate.length > 0 ? candidate : null;
8772
+ };
8773
+ /**
8774
+ * Scroll a tracked-change target into view, falling back to the v2
8775
+ * browser-shell's `focusTrackedChange` (the same mechanism the built-in
8776
+ * comments/tracked-changes sidebar uses via `CommentDialog.vue`'s
8777
+ * `setFocus`) when the shared host geometry path can't resolve the target.
8778
+ * The host's `normalizeGeometryTarget` only understands `text`/`replacement`
8779
+ * targets (enriched with painted `segments`) and never `structural`/
8780
+ * `formatting` targets, so those always fail the primary path and need this
8781
+ * fallback.
8782
+ *
8783
+ * `story`, when present, is threaded into the fallback input as
8784
+ * `trackedChangeStory` — a bare id always resolves to the body story in
8785
+ * `focusTrackedChange`'s target resolver, which would collapse a non-body
8786
+ * (footnote/header/footer) occurrence onto its body counterpart sharing the
8787
+ * same raw id. `importedId`, when present, is threaded in too: the carrier
8788
+ * lookup (`buildTrackChangeCarrierLookupIds`) tries it as an ADDITIONAL alias
8789
+ * alongside the canonical id, for rows whose painted carrier only matches
8790
+ * through a legacy-imported Word revision id, not the canonical public id
8791
+ * (`create-v2-tracked-changes-adapter.test.js` pins this alias path as
8792
+ * necessary for some rows).
8793
+ */
8794
+ const scrollTrackChangeIntoView = async (changeId, target, story, importedId, options) => {
8795
+ const primary = await scrollTargetIntoView(target, options);
8796
+ if (primary.ok) return primary;
8797
+ const trackedChanges = getV2TrackedChanges();
8798
+ const focusTrackedChange = trackedChanges?.focusTrackedChange;
8799
+ if (typeof focusTrackedChange !== "function") return primary;
8800
+ const fallbackInput = story || importedId ? {
8801
+ commentId: changeId,
8802
+ ...story ? { trackedChangeStory: story } : {},
8803
+ ...importedId ? { importedId } : {}
8804
+ } : changeId;
8805
+ try {
8806
+ const fallbackResult = await Promise.resolve(focusTrackedChange.call(trackedChanges, fallbackInput));
8807
+ if (fallbackResult && typeof fallbackResult === "object" && fallbackResult.ok === true) return {
8808
+ success: true,
8809
+ ok: true
8810
+ };
8811
+ } catch {}
8812
+ return primary;
8813
+ };
8814
+ /**
8745
8815
  * Resolve a document entity (comment / tracked change) to its anchor target.
8746
8816
  * Prefers the already-loaded list row, falling back to a live `get({ id })`
8747
8817
  * read when the list does not carry the row.
@@ -9003,9 +9073,11 @@ function createSuperDocUI(options) {
9003
9073
  rollback();
9004
9074
  return { success: false };
9005
9075
  }
9076
+ const activeRow = trackChangesSub.get().items.find((item) => entityRowMatchesRequest(item, activeId, optimisticActiveChange?.story)) ?? trackChangesSub.get().items.find((item) => readEntityId(item) === activeId);
9077
+ const importedId = deriveTrackedChangeImportedId(activeRow);
9006
9078
  trackedChangeNavigationInFlight += 1;
9007
9079
  try {
9008
- const result = await scrollTargetIntoView(target, {
9080
+ const result = await scrollTrackChangeIntoView(activeId, target, optimisticActiveChange?.story, importedId, {
9009
9081
  behavior: "auto",
9010
9082
  shouldContinue: isCurrent
9011
9083
  });
@@ -9129,7 +9201,9 @@ function createSuperDocUI(options) {
9129
9201
  const requestedAtInvalidation = trackChangeRevealInvalidation;
9130
9202
  const loadedItems = trackChangesSub.get().items;
9131
9203
  const publicId = buildTrackedChangeIdContext(loadedItems).toPublicId(changeId) ?? changeId;
9132
- const story = readEntityRequestStory(loadedItems.find((item) => readEntityId(item) === publicId));
9204
+ const matchingRow = loadedItems.find((item) => readEntityId(item) === publicId);
9205
+ const story = readEntityRequestStory(matchingRow);
9206
+ const importedId = deriveTrackedChangeImportedId(matchingRow);
9133
9207
  const previousActiveChange = explicitActiveChange;
9134
9208
  const requestedActiveChange = {
9135
9209
  id: publicId,
@@ -9161,7 +9235,7 @@ function createSuperDocUI(options) {
9161
9235
  recompute();
9162
9236
  trackedChangeNavigationInFlight += 1;
9163
9237
  try {
9164
- const result = await scrollTargetIntoView(target, {
9238
+ const result = await scrollTrackChangeIntoView(publicId, target, story, importedId, {
9165
9239
  behavior: "auto",
9166
9240
  shouldContinue: isCurrent
9167
9241
  });
@@ -3701,6 +3701,7 @@ function createSuperDocUI(options) {
3701
3701
  if (options?.invalidateQueuedNavigation !== false) queuedTrackChangeNavigationInvalidation += 1;
3702
3702
  };
3703
3703
  const listeners = /* @__PURE__ */ new Set();
3704
+ let state;
3704
3705
  let disposed = false;
3705
3706
  let controllerRef = null;
3706
3707
  const asyncReads = /* @__PURE__ */ new Map();
@@ -6666,15 +6667,24 @@ function createSuperDocUI(options) {
6666
6667
  const target = rawTarget && typeof rawTarget === "object" ? rawTarget : null;
6667
6668
  let next = null;
6668
6669
  if (target?.entityType === "trackedChange" && typeof target.entityId === "string" && target.entityId.length > 0) {
6669
- const paintedEntityId = target.entityId;
6670
6670
  const targetStory = target.story && typeof target.story === "object" ? target.story : void 0;
6671
- const allStoryItems = readAllStoryTrackChanges();
6672
- const entityId = (allStoryItems ? targetStory ? buildStoryScopedTrackedChangeIdContext(allStoryItems, targetStory).toPublicId(paintedEntityId) : buildTrackedChangeIdContext(allStoryItems).toPublicId(paintedEntityId) : null) ?? paintedEntityId;
6673
6671
  const story = targetStory && storyLocatorSignature(targetStory) !== storyLocatorSignature(null) ? targetStory : void 0;
6672
+ const rawPaintedEntityId = typeof target.paintedEntityId === "string" && target.paintedEntityId.length > 0 ? target.paintedEntityId : null;
6673
+ const lookupEntityId = rawPaintedEntityId ?? target.entityId;
6674
+ const publicId = (() => {
6675
+ if (story) {
6676
+ const allStoryItems = readAllStoryTrackChanges();
6677
+ const storyContext = allStoryItems ? buildStoryScopedTrackedChangeIdContext(allStoryItems, story) : null;
6678
+ return storyContext?.toPublicId(lookupEntityId) ?? storyContext?.toPublicId(target.entityId) ?? target.entityId;
6679
+ }
6680
+ const bodyContext = buildTrackedChangeIdContext(Array.isArray(state?.trackChanges.items) ? state.trackChanges.items : []);
6681
+ return bodyContext.toPublicId(lookupEntityId) ?? bodyContext.toPublicId(target.entityId) ?? target.entityId;
6682
+ })();
6683
+ const paintedEntityId = rawPaintedEntityId ?? (lookupEntityId !== publicId ? lookupEntityId : null);
6674
6684
  next = {
6675
- id: entityId,
6685
+ id: publicId,
6676
6686
  ...story ? { story } : {},
6677
- ...typeof target.paintedEntityId === "string" && target.paintedEntityId.length > 0 ? { paintedEntityId: target.paintedEntityId } : entityId !== paintedEntityId ? { paintedEntityId } : {}
6687
+ ...paintedEntityId && paintedEntityId !== publicId ? { paintedEntityId } : {}
6678
6688
  };
6679
6689
  }
6680
6690
  if (rawTarget == null && next == null && explicitActiveChange != null && hasPendingTrackChangeRevealFocus(explicitActiveChange)) return false;
@@ -6740,7 +6750,7 @@ function createSuperDocUI(options) {
6740
6750
  };
6741
6751
  syncCoordinatorEditor();
6742
6752
  syncTrackedChangeFocusFromHostReviewTarget(readHostActiveReviewTarget(readHostReviewSource()));
6743
- let state = computeState();
6753
+ state = computeState();
6744
6754
  lastOptimisticInlineSelectionSignature = selectionInlineValueSignature(state.selection);
6745
6755
  const syncHostEventsSubscription = () => {
6746
6756
  const events = (getEditor()?.host)?.events;
@@ -8742,6 +8752,66 @@ function createSuperDocUI(options) {
8742
8752
  }
8743
8753
  };
8744
8754
  /**
8755
+ * Derive the browser-shell's `importedId` carrier-lookup alias from a
8756
+ * public list row's `wordRevisionIds` / `sourceIds` provenance fields,
8757
+ * mirroring `create-v2-tracked-changes-adapter.js`'s own derivation
8758
+ * (`wordRevisionIds.insert ?? .delete ?? .format ?? sourceIds.wordIdInsert
8759
+ * ?? .wordIdDelete ?? .wordIdOther[0]`). A carrier imported from a legacy
8760
+ * Word revision id is painted with THIS id, not the canonical public id —
8761
+ * `focusTrackedChange`'s carrier lookup only tries it when explicitly
8762
+ * passed as `importedId` on an object input (see `scrollTrackChangeIntoView`).
8763
+ */
8764
+ const deriveTrackedChangeImportedId = (row) => {
8765
+ if (!row || typeof row !== "object") return null;
8766
+ const record = row;
8767
+ const wordRevisionIds = record.wordRevisionIds && typeof record.wordRevisionIds === "object" ? record.wordRevisionIds : null;
8768
+ const sourceIds = record.sourceIds && typeof record.sourceIds === "object" ? record.sourceIds : null;
8769
+ const wordIdOther = Array.isArray(sourceIds?.wordIdOther) ? sourceIds.wordIdOther : void 0;
8770
+ const candidate = wordRevisionIds?.insert ?? wordRevisionIds?.delete ?? wordRevisionIds?.format ?? sourceIds?.wordIdInsert ?? sourceIds?.wordIdDelete ?? wordIdOther?.[0];
8771
+ return typeof candidate === "string" && candidate.length > 0 ? candidate : null;
8772
+ };
8773
+ /**
8774
+ * Scroll a tracked-change target into view, falling back to the v2
8775
+ * browser-shell's `focusTrackedChange` (the same mechanism the built-in
8776
+ * comments/tracked-changes sidebar uses via `CommentDialog.vue`'s
8777
+ * `setFocus`) when the shared host geometry path can't resolve the target.
8778
+ * The host's `normalizeGeometryTarget` only understands `text`/`replacement`
8779
+ * targets (enriched with painted `segments`) and never `structural`/
8780
+ * `formatting` targets, so those always fail the primary path and need this
8781
+ * fallback.
8782
+ *
8783
+ * `story`, when present, is threaded into the fallback input as
8784
+ * `trackedChangeStory` — a bare id always resolves to the body story in
8785
+ * `focusTrackedChange`'s target resolver, which would collapse a non-body
8786
+ * (footnote/header/footer) occurrence onto its body counterpart sharing the
8787
+ * same raw id. `importedId`, when present, is threaded in too: the carrier
8788
+ * lookup (`buildTrackChangeCarrierLookupIds`) tries it as an ADDITIONAL alias
8789
+ * alongside the canonical id, for rows whose painted carrier only matches
8790
+ * through a legacy-imported Word revision id, not the canonical public id
8791
+ * (`create-v2-tracked-changes-adapter.test.js` pins this alias path as
8792
+ * necessary for some rows).
8793
+ */
8794
+ const scrollTrackChangeIntoView = async (changeId, target, story, importedId, options) => {
8795
+ const primary = await scrollTargetIntoView(target, options);
8796
+ if (primary.ok) return primary;
8797
+ const trackedChanges = getV2TrackedChanges();
8798
+ const focusTrackedChange = trackedChanges?.focusTrackedChange;
8799
+ if (typeof focusTrackedChange !== "function") return primary;
8800
+ const fallbackInput = story || importedId ? {
8801
+ commentId: changeId,
8802
+ ...story ? { trackedChangeStory: story } : {},
8803
+ ...importedId ? { importedId } : {}
8804
+ } : changeId;
8805
+ try {
8806
+ const fallbackResult = await Promise.resolve(focusTrackedChange.call(trackedChanges, fallbackInput));
8807
+ if (fallbackResult && typeof fallbackResult === "object" && fallbackResult.ok === true) return {
8808
+ success: true,
8809
+ ok: true
8810
+ };
8811
+ } catch {}
8812
+ return primary;
8813
+ };
8814
+ /**
8745
8815
  * Resolve a document entity (comment / tracked change) to its anchor target.
8746
8816
  * Prefers the already-loaded list row, falling back to a live `get({ id })`
8747
8817
  * read when the list does not carry the row.
@@ -9003,9 +9073,11 @@ function createSuperDocUI(options) {
9003
9073
  rollback();
9004
9074
  return { success: false };
9005
9075
  }
9076
+ const activeRow = trackChangesSub.get().items.find((item) => entityRowMatchesRequest(item, activeId, optimisticActiveChange?.story)) ?? trackChangesSub.get().items.find((item) => readEntityId(item) === activeId);
9077
+ const importedId = deriveTrackedChangeImportedId(activeRow);
9006
9078
  trackedChangeNavigationInFlight += 1;
9007
9079
  try {
9008
- const result = await scrollTargetIntoView(target, {
9080
+ const result = await scrollTrackChangeIntoView(activeId, target, optimisticActiveChange?.story, importedId, {
9009
9081
  behavior: "auto",
9010
9082
  shouldContinue: isCurrent
9011
9083
  });
@@ -9129,7 +9201,9 @@ function createSuperDocUI(options) {
9129
9201
  const requestedAtInvalidation = trackChangeRevealInvalidation;
9130
9202
  const loadedItems = trackChangesSub.get().items;
9131
9203
  const publicId = buildTrackedChangeIdContext(loadedItems).toPublicId(changeId) ?? changeId;
9132
- const story = readEntityRequestStory(loadedItems.find((item) => readEntityId(item) === publicId));
9204
+ const matchingRow = loadedItems.find((item) => readEntityId(item) === publicId);
9205
+ const story = readEntityRequestStory(matchingRow);
9206
+ const importedId = deriveTrackedChangeImportedId(matchingRow);
9133
9207
  const previousActiveChange = explicitActiveChange;
9134
9208
  const requestedActiveChange = {
9135
9209
  id: publicId,
@@ -9161,7 +9235,7 @@ function createSuperDocUI(options) {
9161
9235
  recompute();
9162
9236
  trackedChangeNavigationInFlight += 1;
9163
9237
  try {
9164
- const result = await scrollTargetIntoView(target, {
9238
+ const result = await scrollTrackChangeIntoView(publicId, target, story, importedId, {
9165
9239
  behavior: "auto",
9166
9240
  shouldContinue: isCurrent
9167
9241
  });
@@ -19,7 +19,7 @@ var COLLABORATION_UPGRADE_ENGINE_MINIMUM_NODE_MAJOR = 20;
19
19
  var PRIVATE_ENGINE_INFO = (0, _superdoc_docx_engine_collaboration_upgrade_engine.getCollaborationUpgradeEngineInfo)();
20
20
  var ENGINE_INFO = Object.freeze({
21
21
  ...PRIVATE_ENGINE_INFO,
22
- superdocVersion: "2.4.0-next.42",
22
+ superdocVersion: "2.4.0-next.44",
23
23
  roomSchemaVersion: Object.freeze({ ...PRIVATE_ENGINE_INFO.roomSchemaVersion }),
24
24
  supportedBundleVersions: SUPPORTED_COLLABORATION_UPGRADE_BUNDLE_VERSIONS,
25
25
  supportedV1ReaderContractVersions: SUPPORTED_V1_READER_CONTRACT_VERSIONS
@@ -18,7 +18,7 @@ var COLLABORATION_UPGRADE_ENGINE_MINIMUM_NODE_MAJOR = 20;
18
18
  var PRIVATE_ENGINE_INFO = getCollaborationUpgradeEngineInfo$1();
19
19
  var ENGINE_INFO = Object.freeze({
20
20
  ...PRIVATE_ENGINE_INFO,
21
- superdocVersion: "2.4.0-next.42",
21
+ superdocVersion: "2.4.0-next.44",
22
22
  roomSchemaVersion: Object.freeze({ ...PRIVATE_ENGINE_INFO.roomSchemaVersion }),
23
23
  supportedBundleVersions: SUPPORTED_COLLABORATION_UPGRADE_BUNDLE_VERSIONS,
24
24
  supportedV1ReaderContractVersions: SUPPORTED_V1_READER_CONTRACT_VERSIONS
@@ -742,6 +742,16 @@ export type CellBorders = {
742
742
  bottom?: BorderSpec;
743
743
  left?: BorderSpec;
744
744
  };
745
+ /** True when a normalized border contributes a visible edge. */
746
+ export declare const isPresentBorder: (border?: BorderSpec) => border is BorderSpec;
747
+ /** True when the source explicitly suppresses an edge rather than omitting it. */
748
+ export declare const isExplicitNoneBorder: (border?: unknown) => boolean;
749
+ /**
750
+ * Resolve the single visible edge shared by two cells according to
751
+ * ECMA-376 Part 1 §17.4.66. Kept in the editor-neutral contract package so
752
+ * import and DOM paint use one conflict algorithm.
753
+ */
754
+ export declare const resolveBorderConflict: (first?: BorderSpec, second?: BorderSpec) => BorderSpec | undefined;
745
755
  export type TableCellAttrs = {
746
756
  borders?: CellBorders;
747
757
  padding?: BoxSpacing;
@@ -974,6 +984,13 @@ export type TextFormatting = {
974
984
  export type TextPart = {
975
985
  text: string;
976
986
  formatting?: TextFormatting;
987
+ /**
988
+ * Paragraph properties carried by the first visible part of a shape-text
989
+ * paragraph. Shape text is intentionally flattened into `parts`, so this
990
+ * marker preserves the authored block geometry without introducing a second
991
+ * competing text model.
992
+ */
993
+ paragraphProperties?: ShapeTextParagraphProperties;
977
994
  /** Optional field token (e.g., PAGE/NUMPAGES/SECTIONPAGES) resolved at render time. */
978
995
  fieldType?: 'PAGE' | 'NUMPAGES' | 'SECTIONPAGES';
979
996
  /** PAGE/SECTIONPAGES field-local value formatting override. */
@@ -999,6 +1016,17 @@ export type TextPart = {
999
1016
  height?: number;
1000
1017
  alt?: string;
1001
1018
  };
1019
+ /** Paragraph geometry used by flattened DrawingML/VML shape text. */
1020
+ export type ShapeTextParagraphProperties = {
1021
+ horizontalAlign?: 'left' | 'center' | 'right' | 'justify';
1022
+ spacingBefore?: number;
1023
+ spacingAfter?: number;
1024
+ line?: number;
1025
+ lineUnit?: 'px' | 'multiplier';
1026
+ leftIndent?: number;
1027
+ rightIndent?: number;
1028
+ firstLineIndent?: number;
1029
+ };
1002
1030
  /** Text content configuration for shapes. */
1003
1031
  export type ShapeTextContent = {
1004
1032
  /** Array of text parts with individual formatting. */
@@ -1025,6 +1053,8 @@ export type VectorShapeStyle = {
1025
1053
  fillColor?: FillColor;
1026
1054
  strokeColor?: StrokeColor;
1027
1055
  strokeWidth?: number;
1056
+ /** Optional per-edge frame used by render-only table cells inside legacy shape groups. */
1057
+ borders?: CellBorders;
1028
1058
  lineEnds?: LineEnds;
1029
1059
  textContent?: ShapeTextContent;
1030
1060
  textAlign?: string;
@@ -1124,6 +1154,8 @@ export type VectorShapeDrawing = DrawingBlockBase & {
1124
1154
  export type TextboxDrawing = DrawingBlockBase & {
1125
1155
  drawingKind: 'textboxShape';
1126
1156
  geometry: DrawingGeometry;
1157
+ /** `a:spAutoFit`: resize the shape height to its measured text content. */
1158
+ autoFit?: boolean;
1127
1159
  shapeKind?: string;
1128
1160
  customGeometry?: CustomGeometryData;
1129
1161
  fillColor?: FillColor;
@@ -1312,6 +1344,8 @@ export type ColumnBreakBlock = {
1312
1344
  /** Positioning for anchored images (offsets in CSS px). */
1313
1345
  export type ImageAnchor = {
1314
1346
  isAnchored?: boolean;
1347
+ /** Keep the anchored object inside its table cell and resize the cell as needed. */
1348
+ layoutInCell?: boolean;
1315
1349
  hRelativeFrom?: 'column' | 'page' | 'margin';
1316
1350
  vRelativeFrom?: 'paragraph' | 'page' | 'margin';
1317
1351
  alignH?: 'left' | 'center' | 'right';
@@ -1418,7 +1452,7 @@ export type ExclusionZone = {
1418
1452
  left: number;
1419
1453
  right: number;
1420
1454
  };
1421
- wrapMode: 'left' | 'right' | 'both' | 'none' | 'largest';
1455
+ wrapMode: 'left' | 'right' | 'both' | 'none' | 'largest' | 'topBottom';
1422
1456
  polygon?: number[][];
1423
1457
  };
1424
1458
  export type ParagraphSpacing = {
@@ -1930,6 +1964,18 @@ export type Line = {
1930
1964
  */
1931
1965
  tabWidths?: Record<number, number>;
1932
1966
  };
1967
+ /**
1968
+ * One contiguous horizontal region available to a measured paragraph line.
1969
+ *
1970
+ * Coordinates are relative to the paragraph fragment's content origin. A line
1971
+ * can expose multiple regions when text wraps on both sides of a floating
1972
+ * object. Measuring owns the line breaks inside these regions; layout owns
1973
+ * deriving the regions from page/column float geometry.
1974
+ */
1975
+ export type ParagraphLineRegion = {
1976
+ offsetX: number;
1977
+ width: number;
1978
+ };
1933
1979
  export type LineSegment = {
1934
1980
  runIndex: number;
1935
1981
  fromChar: number;
@@ -1,4 +1,4 @@
1
- import { FlowBlock, HeaderFooterLayout, Measure, PageNumberChapterSeparator, PageNumberFormat, ParagraphBlock, ParagraphMeasure } from '../../contracts/src/index.js';
1
+ import { FlowBlock, HeaderFooterLayout, Measure, PageNumberChapterSeparator, PageNumberFormat, ParagraphBlock, ParagraphLineRegion, ParagraphMeasure } from '../../contracts/src/index.js';
2
2
  import { HeaderFooterConstraints, LayoutExecutionCheckpoint, LayoutExecutionControl } from '../../layout-engine/src/index.js';
3
3
  import { MeasureCache } from './cache.js';
4
4
  import { ResolveHeaderFooterTokensOptions } from './resolveHeaderFooterTokens.js';
@@ -127,4 +127,4 @@ export declare function invalidateHeaderFooterMeasureCache(blockIds: string[]):
127
127
  * @param pageResolver - Callback to resolve display page info for a physical page number
128
128
  * @returns Batch result with layouts, blocks, and measures for each variant
129
129
  */
130
- export declare function layoutHeaderFooterWithCache(sections: HeaderFooterBatch, constraints: HeaderFooterConstraints, measureBlock: MeasureResolver, cache?: HeaderFooterLayoutCache, totalPages?: number, pageResolver?: PageResolver, kind?: 'header' | 'footer', fontSignature?: string, remeasureParagraph?: (block: ParagraphBlock, maxWidth: number, firstLineIndent?: number) => ParagraphMeasure, options?: ResolveHeaderFooterTokensOptions, execution?: HeaderFooterLayoutExecution): Promise<HeaderFooterBatchResult>;
130
+ export declare function layoutHeaderFooterWithCache(sections: HeaderFooterBatch, constraints: HeaderFooterConstraints, measureBlock: MeasureResolver, cache?: HeaderFooterLayoutCache, totalPages?: number, pageResolver?: PageResolver, kind?: 'header' | 'footer', fontSignature?: string, remeasureParagraph?: (block: ParagraphBlock, maxWidth: number, firstLineIndent?: number, lineRegions?: readonly (readonly ParagraphLineRegion[])[]) => ParagraphMeasure, options?: ResolveHeaderFooterTokensOptions, execution?: HeaderFooterLayoutExecution): Promise<HeaderFooterBatchResult>;
@@ -1,4 +1,4 @@
1
- import { ParagraphBlock, ParagraphMeasure } from '../../contracts/src/index.js';
1
+ import { ParagraphBlock, ParagraphMeasure, ParagraphLineRegion } from '../../contracts/src/index.js';
2
2
  /** Drop cached text widths and the canvas context. Call when registered font faces may have changed. */
3
3
  export declare function clearRemeasureTextCaches(): void;
4
4
  /**
@@ -72,4 +72,4 @@ export declare function clearRemeasureTextCaches(): void;
72
72
  * // First line has only 170px available (200 - 30), subsequent lines have full 200px
73
73
  * ```
74
74
  */
75
- export declare function remeasureParagraph(block: ParagraphBlock, maxWidth: number, firstLineIndent?: number): ParagraphMeasure;
75
+ export declare function remeasureParagraph(block: ParagraphBlock, maxWidth: number, firstLineIndent?: number, lineRegions?: readonly (readonly ParagraphLineRegion[])[]): ParagraphMeasure;
@@ -31,6 +31,19 @@ export type FloatingObjectManager = {
31
31
  width: number;
32
32
  offsetX: number;
33
33
  };
34
+ /**
35
+ * Compute every contiguous horizontal region available to a line.
36
+ * Unlike computeAvailableWidth, this preserves both sides of a centered
37
+ * `wrapText="bothSides"` object instead of collapsing them to one side.
38
+ */
39
+ computeAvailableRegions(lineY: number, lineHeight: number, baseWidth: number, columnIndex: number, pageNumber: number): Array<{
40
+ offsetX: number;
41
+ width: number;
42
+ }>;
43
+ /**
44
+ * Return the first Y coordinate below every overlapping TopAndBottom float.
45
+ */
46
+ computeVerticalClearance(lineY: number, lineHeight: number, columnIndex: number, pageNumber: number): number | null;
34
47
  /**
35
48
  * Get all floating images for a page (for debugging/painting).
36
49
  */
@@ -1,4 +1,4 @@
1
- import { ColumnLayout, FlowBlock, HeaderFooterLayout, Layout, Measure, Page, ParagraphBlock, ParagraphMeasure, SectionMetadata, FlowMode, DocumentBackground } from '../../contracts/src/index.js';
1
+ import { ColumnLayout, FlowBlock, HeaderFooterLayout, Layout, Measure, Page, ParagraphBlock, ParagraphMeasure, SectionMetadata, FlowMode, DocumentBackground, ParagraphLineRegion } from '../../contracts/src/index.js';
2
2
  import { PageState, ConstraintBoundary } from './paginator.js';
3
3
  import { LayoutExecutionControl } from './execution.js';
4
4
  type PageSize = {
@@ -31,7 +31,7 @@ export type LayoutOptions = {
31
31
  marginTop?: number;
32
32
  marginBottom?: number;
33
33
  };
34
- remeasureParagraph?: (block: ParagraphBlock, maxWidth: number, firstLineIndent?: number) => ParagraphMeasure;
34
+ remeasureParagraph?: (block: ParagraphBlock, maxWidth: number, firstLineIndent?: number, lineRegions?: readonly (readonly ParagraphLineRegion[])[]) => ParagraphMeasure;
35
35
  /** @internal Positioned paragraph frames that render without advancing the story cursor. */
36
36
  nonFlowPositionedParagraphFrameIds?: ReadonlySet<string>;
37
37
  sectionMetadata?: SectionMetadata[];
@@ -337,8 +337,8 @@ export declare function layoutDocumentRange(input: LayoutRangeInput): LayoutRang
337
337
  * Returns separate measurement bounds (for pagination) and render bounds
338
338
  * (for overlay shift). See the Coordinate Contract in the fix plan for details.
339
339
  */
340
- export declare function layoutHeaderFooter(blocks: FlowBlock[], measures: Measure[], constraints: HeaderFooterConstraints, kind?: 'header' | 'footer', remeasureParagraph?: (block: ParagraphBlock, maxWidth: number, firstLineIndent?: number) => ParagraphMeasure): HeaderFooterLayout;
341
- export declare function layoutHeaderFooterCooperatively(blocks: FlowBlock[], measures: Measure[], constraints: HeaderFooterConstraints, kind?: 'header' | 'footer', remeasureParagraph?: (block: ParagraphBlock, maxWidth: number, firstLineIndent?: number) => ParagraphMeasure, execution?: LayoutExecutionControl): Promise<HeaderFooterLayout>;
340
+ export declare function layoutHeaderFooter(blocks: FlowBlock[], measures: Measure[], constraints: HeaderFooterConstraints, kind?: 'header' | 'footer', remeasureParagraph?: LayoutOptions['remeasureParagraph']): HeaderFooterLayout;
341
+ export declare function layoutHeaderFooterCooperatively(blocks: FlowBlock[], measures: Measure[], constraints: HeaderFooterConstraints, kind?: 'header' | 'footer', remeasureParagraph?: LayoutOptions['remeasureParagraph'], execution?: LayoutExecutionControl): Promise<HeaderFooterLayout>;
342
342
  export { buildAnchorMap, resolvePageRefTokens, getTocBlocksForRemeasurement } from './resolvePageRefs.js';
343
343
  export { buildChapterContextByPage, buildChapterContextByPageCooperatively, computeDisplayPageNumber, computeDisplayPageNumberCooperatively, formatPageNumber, formatPageNumberFieldValue, formatSectionPageNumberText, normalizeChapterMarkerText, } from './pageNumbering.js';
344
344
  export type { ChapterPageInfo, DisplayPageInfo, PageNumberFormat } from './pageNumbering.js';
@@ -1,6 +1,6 @@
1
1
  import { FloatingObjectManager } from './floating-objects.js';
2
2
  import { PageState } from './paginator.js';
3
- import { PageMargins, ParagraphBlock, ParagraphMeasure, ImageBlock, ImageMeasure, DrawingBlock, DrawingMeasure } from '../../contracts/src/index.js';
3
+ import { PageMargins, ParagraphBlock, ParagraphMeasure, ImageBlock, ImageMeasure, DrawingBlock, DrawingMeasure, ParagraphLineRegion } from '../../contracts/src/index.js';
4
4
  import { AnchoredTable } from './anchors.js';
5
5
  /**
6
6
  * SD-2656: ordered footnote anchor entry. The body slicer reads the candidate
@@ -21,7 +21,7 @@ export type ParagraphLayoutContext = {
21
21
  advanceColumn: (state: PageState) => PageState;
22
22
  columnX: (state: PageState, columnIndex?: number) => number;
23
23
  floatManager: FloatingObjectManager;
24
- remeasureParagraph?: (block: ParagraphBlock, maxWidth: number, firstLineIndent?: number) => ParagraphMeasure;
24
+ remeasureParagraph?: (block: ParagraphBlock, maxWidth: number, firstLineIndent?: number, lineRegions?: readonly (readonly ParagraphLineRegion[])[]) => ParagraphMeasure;
25
25
  /**
26
26
  * Override the paragraph's spacing-after value. Used when contextual spacing
27
27
  * should suppress spacing between this paragraph and the next (same-style) paragraph.
@@ -79,32 +79,9 @@ export declare const borderValueToSpec: (value?: TableBorderValue | null) => Bor
79
79
  * ```
80
80
  */
81
81
  export declare const resolveTableBorderValue: (explicit: TableBorderValue | undefined | null, fallback?: TableBorderValue | undefined | null) => BorderSpec | undefined;
82
- export declare const isPresentBorder: (b?: BorderSpec) => b is BorderSpec;
83
- /**
84
- * True when a border is EXPLICITLY set to none/nil (`w:val="nil"`/`"none"`), as opposed to
85
- * simply unset/absent. The distinction matters for shared interior edges (§17.4.66): an
86
- * explicit none on BOTH adjacent cells suppresses the divider, while an unset side inherits
87
- * the table's insideH/insideV. Accepts either a CellBorders BorderSpec (`{style:'none'}`) or
88
- * a TableBorderValue (`{none:true}`).
89
- */
90
- export declare const isExplicitNoneBorder: (b?: unknown) => boolean;
91
- /**
92
- * OOXML cell-border conflict resolution (ECMA-376 §17.4.66).
93
- *
94
- * With zero cell spacing, two cells sharing an edge each specify a border; the spec
95
- * collapses them to a SINGLE displayed border:
96
- * 1. If either side is nil/none/absent, the opposing (present) border is displayed.
97
- * 2. Otherwise the border with greater weight wins, where
98
- * weight = (#lines in the style) × (style number).
99
- * 3. Equal weight → the style higher on the precedence list (single first) wins.
100
- * 4. Identical style → the color with the smaller brightness (R+B+2G, then B+2G, then
101
- * G) wins; finally the first border (reading order) wins.
102
- *
103
- * @param a - One side's border (the owning cell's, e.g. the lower/right cell)
104
- * @param b - The opposing side's border (e.g. the upper/left neighbor)
105
- * @returns The single BorderSpec to display, or undefined if neither is present.
106
- */
107
- export declare const resolveBorderConflict: (a?: BorderSpec, b?: BorderSpec) => BorderSpec | undefined;
82
+ export declare const isPresentBorder: (border?: BorderSpec) => border is BorderSpec;
83
+ export declare const isExplicitNoneBorder: (border?: unknown) => boolean;
84
+ export declare const resolveBorderConflict: (first?: BorderSpec, second?: BorderSpec) => BorderSpec | undefined;
108
85
  /**
109
86
  * Creates a border overlay element for a table fragment.
110
87
  *
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_create_super_doc_ui = require("../chunks/create-super-doc-ui-Dmv8Hjs6.cjs");
2
+ const require_create_super_doc_ui = require("../chunks/create-super-doc-ui-C7loo2YE.cjs");
3
3
  let react = require("react");
4
4
  //#region src/public/ui/react.ts
5
5
  /**
@@ -1,4 +1,4 @@
1
- import { t as createSuperDocUI } from "../chunks/create-super-doc-ui-B5SqT0gL.es.js";
1
+ import { t as createSuperDocUI } from "../chunks/create-super-doc-ui-C6qsVKMY.es.js";
2
2
  import { createContext, createElement, useCallback, useContext, useEffect, useRef, useState } from "react";
3
3
  //#region src/public/ui/react.ts
4
4
  /**
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_create_super_doc_ui = require("../chunks/create-super-doc-ui-Dmv8Hjs6.cjs");
2
+ const require_create_super_doc_ui = require("../chunks/create-super-doc-ui-C7loo2YE.cjs");
3
3
  exports.BUILT_IN_COMMAND_IDS = require_create_super_doc_ui.BUILT_IN_COMMAND_IDS;
4
4
  exports.createSuperDocUI = require_create_super_doc_ui.createSuperDocUI;
5
5
  exports.shallowEqual = require_create_super_doc_ui.shallowEqual;
@@ -1,2 +1,2 @@
1
- import { n as shallowEqual, r as BUILT_IN_COMMAND_IDS, t as createSuperDocUI } from "../chunks/create-super-doc-ui-B5SqT0gL.es.js";
1
+ import { n as shallowEqual, r as BUILT_IN_COMMAND_IDS, t as createSuperDocUI } from "../chunks/create-super-doc-ui-C6qsVKMY.es.js";
2
2
  export { BUILT_IN_COMMAND_IDS, createSuperDocUI, shallowEqual };
package/dist/superdoc.cjs CHANGED
@@ -4,7 +4,7 @@ const require_blank_docx = require("./chunks/blank-docx-BuFAbRjs.cjs");
4
4
  const require_eventemitter3 = require("./chunks/eventemitter3-D38u8ta_.cjs");
5
5
  const require_uuid = require("./chunks/uuid-BhG0ngwk.cjs");
6
6
  const require_jszip = require("./chunks/jszip-RC64TPzL.cjs");
7
- const require_create_super_doc_ui = require("./chunks/create-super-doc-ui-Dmv8Hjs6.cjs");
7
+ const require_create_super_doc_ui = require("./chunks/create-super-doc-ui-C7loo2YE.cjs");
8
8
  const require__plugin_vue_export_helper = require("./chunks/_plugin-vue_export-helper-SDR04tiH.cjs");
9
9
  const require_constants = require("./chunks/constants-DpXuDx_g.cjs");
10
10
  let vue = require("vue");
@@ -43260,7 +43260,7 @@ var SuperDoc = class extends require_eventemitter3.import_eventemitter3.default
43260
43260
  this.config.colors = shuffleArray(this.config.colors);
43261
43261
  this.userColorMap = /* @__PURE__ */ new Map();
43262
43262
  this.colorIndex = 0;
43263
- this.version = "2.4.0-next.42";
43263
+ this.version = "2.4.0-next.44";
43264
43264
  this.#log("🦋 [superdoc] Using SuperDoc version:", this.version);
43265
43265
  this.superdocId = config.superdocId || require_uuid.v4();
43266
43266
  this.colors = this.config.colors ?? [];
@@ -3,7 +3,7 @@ import { t as blank_default } from "./chunks/blank-docx-DzQccOlW.es.js";
3
3
  import { t as import_eventemitter3 } from "./chunks/eventemitter3-CMOjDs74.es.js";
4
4
  import { t as v4 } from "./chunks/uuid-H0Xcmmhy.es.js";
5
5
  import { a as init_dist$1, i as global, n as init_dist$2, o as Buffer, r as process$1, s as init_dist, t as require_jszip_min } from "./chunks/jszip-VxCFV9YS.es.js";
6
- import { a as DOM_CLASS_NAMES, i as getV2TrackedChangeMutationImpact, t as createSuperDocUI } from "./chunks/create-super-doc-ui-B5SqT0gL.es.js";
6
+ import { a as DOM_CLASS_NAMES, i as getV2TrackedChangeMutationImpact, t as createSuperDocUI } from "./chunks/create-super-doc-ui-C6qsVKMY.es.js";
7
7
  import { t as _plugin_vue_export_helper_default } from "./chunks/_plugin-vue_export-helper-BOaGB7Aw.es.js";
8
8
  import { t as PDF_TO_CSS_UNITS } from "./chunks/constants-B6VBlmKp.es.js";
9
9
  import * as Vue from "vue";
@@ -43193,7 +43193,7 @@ var SuperDoc = class extends import_eventemitter3.default {
43193
43193
  this.config.colors = shuffleArray(this.config.colors);
43194
43194
  this.userColorMap = /* @__PURE__ */ new Map();
43195
43195
  this.colorIndex = 0;
43196
- this.version = "2.4.0-next.42";
43196
+ this.version = "2.4.0-next.44";
43197
43197
  this.#log("🦋 [superdoc] Using SuperDoc version:", this.version);
43198
43198
  this.superdocId = config.superdocId || v4();
43199
43199
  this.colors = this.config.colors ?? [];