@beyondwork/docx-react-component 1.0.105 → 1.0.108
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/package.json +19 -5
- package/src/api/geometry-overlay-rects.ts +5 -0
- package/src/api/package-version.ts +1 -1
- package/src/api/page-anchor-id.ts +5 -0
- package/src/api/public-types.ts +16 -9
- package/src/api/table-node-specs.ts +6 -0
- package/src/api/v3/_create.ts +10 -2
- package/src/api/v3/_page-anchor-id.ts +52 -0
- package/src/api/v3/_runtime-handle.ts +92 -1
- package/src/api/v3/ai/_audit-reference.ts +28 -0
- package/src/api/v3/ai/_audit-time.ts +5 -0
- package/src/api/v3/ai/_pe2-evidence.ts +310 -6
- package/src/api/v3/ai/attach.ts +29 -4
- package/src/api/v3/ai/bundle.ts +6 -2
- package/src/api/v3/ai/inspect.ts +6 -2
- package/src/api/v3/ai/replacement.ts +112 -18
- package/src/api/v3/ai/resolve.ts +2 -2
- package/src/api/v3/ai/review.ts +177 -3
- package/src/api/v3/index.ts +8 -0
- package/src/api/v3/runtime/collab.ts +462 -0
- package/src/api/v3/runtime/document.ts +503 -20
- package/src/api/v3/runtime/geometry.ts +97 -0
- package/src/api/v3/runtime/layout.ts +744 -0
- package/src/api/v3/runtime/perf-probe.ts +14 -0
- package/src/api/v3/runtime/viewport.ts +9 -8
- package/src/api/v3/ui/_types.ts +202 -55
- package/src/api/v3/ui/chrome-preset-model.ts +5 -5
- package/src/api/v3/ui/debug.ts +115 -2
- package/src/api/v3/ui/index.ts +17 -0
- package/src/api/v3/ui/overlays.ts +0 -8
- package/src/api/v3/ui/surface.ts +56 -0
- package/src/api/v3/ui/viewport.ts +119 -9
- package/src/core/commands/image-commands.ts +1 -0
- package/src/core/commands/index.ts +6 -0
- package/src/core/schema/text-schema.ts +43 -5
- package/src/core/selection/mapping.ts +8 -1
- package/src/core/selection/review-anchors.ts +5 -1
- package/src/core/state/text-transaction.ts +8 -2
- package/src/io/export/serialize-revisions.ts +149 -1
- package/src/io/normalize/normalize-text.ts +6 -0
- package/src/io/ooxml/parse-bookmark-references.ts +55 -0
- package/src/io/ooxml/parse-fields.ts +24 -2
- package/src/io/ooxml/parse-headers-footers.ts +38 -5
- package/src/io/ooxml/parse-main-document.ts +153 -9
- package/src/io/ooxml/parse-numbering.ts +20 -0
- package/src/io/ooxml/parse-revisions.ts +19 -8
- package/src/io/opc/package-reader.ts +98 -8
- package/src/model/anchor.ts +4 -3
- package/src/model/canonical-document.ts +220 -2
- package/src/model/canonical-hash.ts +221 -0
- package/src/model/canonical-layout-inputs.ts +245 -6
- package/src/model/layout/index.ts +1 -0
- package/src/model/layout/page-graph-types.ts +147 -1
- package/src/model/review/revision-types.ts +14 -3
- package/src/preservation/store.ts +20 -4
- package/src/review/README.md +1 -1
- package/src/review/store/revision-actions.ts +14 -2
- package/src/runtime/collab/event-types.ts +67 -1
- package/src/runtime/collab/runtime-collab-sync.ts +177 -5
- package/src/runtime/diagnostics/layout-guard-warning.ts +18 -0
- package/src/runtime/document-heading-outline.ts +147 -0
- package/src/runtime/document-navigation.ts +8 -243
- package/src/runtime/document-runtime.ts +279 -115
- package/src/runtime/edit-dispatch/dispatch-text-command.ts +11 -0
- package/src/runtime/formatting/layout-inputs.ts +38 -5
- package/src/runtime/formatting/numbering/geometry.ts +28 -2
- package/src/runtime/geometry/adjacent-geometry-intake.ts +835 -0
- package/src/runtime/geometry/caret-geometry.ts +5 -6
- package/src/runtime/geometry/geometry-facet.ts +60 -10
- package/src/runtime/geometry/geometry-index.ts +661 -16
- package/src/runtime/geometry/geometry-types.ts +59 -0
- package/src/runtime/geometry/hit-test.ts +11 -1
- package/src/runtime/geometry/overlay-rects.ts +5 -3
- package/src/runtime/geometry/project-anchors.ts +1 -1
- package/src/runtime/geometry/word-layout-v2-line-intake.ts +323 -0
- package/src/runtime/layout/index.ts +6 -0
- package/src/runtime/layout/layout-engine-instance.ts +6 -1
- package/src/runtime/layout/layout-engine-version.ts +188 -16
- package/src/runtime/layout/layout-facet-types.ts +6 -0
- package/src/runtime/layout/page-graph.ts +23 -4
- package/src/runtime/layout/paginated-layout-engine.ts +149 -15
- package/src/runtime/layout/project-block-fragments.ts +351 -14
- package/src/runtime/layout/public-facet.ts +162 -24
- package/src/runtime/layout/table-row-continuation-contract.ts +107 -0
- package/src/runtime/layout/table-row-split.ts +92 -35
- package/src/runtime/prerender/cache-envelope.ts +2 -2
- package/src/runtime/prerender/cache-key.ts +5 -4
- package/src/runtime/prerender/customxml-cache.ts +0 -1
- package/src/runtime/render/render-kernel.ts +1 -1
- package/src/runtime/revision-runtime.ts +112 -10
- package/src/runtime/scopes/_scope-dependencies.ts +1 -0
- package/src/runtime/scopes/action-validation.ts +22 -2
- package/src/runtime/scopes/capabilities.ts +316 -0
- package/src/runtime/scopes/compile-scope-bundle.ts +14 -0
- package/src/runtime/scopes/compiler-service.ts +108 -4
- package/src/runtime/scopes/content-control-evidence.ts +79 -0
- package/src/runtime/scopes/create-issue.ts +5 -5
- package/src/runtime/scopes/evidence.ts +91 -0
- package/src/runtime/scopes/formatting/apply.ts +2 -0
- package/src/runtime/scopes/geometry-evidence.ts +130 -0
- package/src/runtime/scopes/index.ts +54 -0
- package/src/runtime/scopes/issue-lifecycle.ts +224 -0
- package/src/runtime/scopes/layout-evidence.ts +374 -0
- package/src/runtime/scopes/multi-paragraph-refusal.ts +37 -0
- package/src/runtime/scopes/preservation-boundary.ts +7 -1
- package/src/runtime/scopes/replacement/apply.ts +97 -34
- package/src/runtime/scopes/scope-kinds/paragraph.ts +108 -12
- package/src/runtime/scopes/semantic-scope-types.ts +242 -3
- package/src/runtime/scopes/visualization.ts +28 -0
- package/src/runtime/surface-projection.ts +44 -5
- package/src/runtime/telemetry/perf-probe.ts +216 -0
- package/src/runtime/virtualized-rendering.ts +36 -1
- package/src/runtime/workflow/ai-issue-lifecycle.ts +253 -0
- package/src/runtime/workflow/coordinator.ts +39 -11
- package/src/runtime/workflow/derived-scope-resolver.ts +63 -9
- package/src/runtime/workflow/index.ts +4 -0
- package/src/runtime/workflow/overlay-lane-types.ts +58 -0
- package/src/runtime/workflow/overlay-lanes.ts +386 -0
- package/src/runtime/workflow/overlay-store.ts +2 -2
- package/src/runtime/workflow/redline-posture-calibration.ts +257 -0
- package/src/runtime/workflow/word-field-matrix-calibration.ts +231 -0
- package/src/session/_sync-legacy.ts +17 -27
- package/src/session/import/loader.ts +6 -4
- package/src/session/import/source-package-evidence.ts +186 -2
- package/src/session/index.ts +5 -6
- package/src/session/session.ts +30 -56
- package/src/session/types.ts +8 -13
- package/src/shell/session-bootstrap.ts +155 -81
- package/src/ui/WordReviewEditor.tsx +520 -12
- package/src/ui/editor-shell-view.tsx +14 -4
- package/src/ui/editor-surface-controller.tsx +5 -3
- package/src/ui/headless/selection-tool-resolver.ts +1 -2
- package/src/ui/presence-overlay-lane.ts +130 -0
- package/src/ui/ui-controller-factory.ts +17 -0
- package/src/ui-tailwind/chrome/build-context-menu-entries.ts +5 -1
- package/src/ui-tailwind/chrome/editor-action-registry.ts +105 -5
- package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +7 -0
- package/src/ui-tailwind/chrome/layer-debug-contracts.ts +208 -0
- package/src/ui-tailwind/chrome/resolve-target-kind.ts +13 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +11 -3
- package/src/ui-tailwind/chrome/tw-command-palette.tsx +36 -6
- package/src/ui-tailwind/chrome/tw-context-menu.tsx +6 -1
- package/src/ui-tailwind/chrome/tw-display-mode-selector.tsx +42 -109
- package/src/ui-tailwind/chrome/tw-inline-find-bar.tsx +26 -6
- package/src/ui-tailwind/chrome/tw-navigation-command-bar.tsx +328 -0
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +8 -4
- package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +129 -1
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +19 -5
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +5 -1
- package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +28 -12
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +30 -3
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +116 -10
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +223 -94
- package/src/ui-tailwind/chrome-overlay/tw-presence-overlay-lane.tsx +157 -0
- package/src/ui-tailwind/chrome-overlay/tw-review-overlay-lane-markers.tsx +259 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +5 -2
- package/src/ui-tailwind/chrome-overlay/tw-substrate-overlay-lanes.tsx +314 -0
- package/src/ui-tailwind/debug/README.md +4 -1
- package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +272 -0
- package/src/ui-tailwind/debug/layer11-word-field-matrix-evidence.ts +160 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +14 -215
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +42 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +38 -2
- package/src/ui-tailwind/editor-surface/pm-schema.ts +14 -4
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +34 -5
- package/src/ui-tailwind/editor-surface/runtime-decoration-plugin.ts +9 -19
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -2
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +145 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +16 -11
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +8 -10
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +3 -0
- package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +4 -2
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +60 -20
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +16 -11
- package/src/ui-tailwind/review/tw-health-panel.tsx +36 -17
- package/src/ui-tailwind/review/tw-review-rail.tsx +7 -4
- package/src/ui-tailwind/review-workspace/diagnostics-visibility.ts +44 -0
- package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +11 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +16 -1
- package/src/ui-tailwind/review-workspace/types.ts +26 -12
- package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +40 -11
- package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +2 -1
- package/src/ui-tailwind/review-workspace/use-page-markers.ts +15 -26
- package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +35 -18
- package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +41 -32
- package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +2 -1
- package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +2 -1
- package/src/ui-tailwind/status/tw-status-bar.tsx +6 -5
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +52 -80
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +12 -48
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +9 -4
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +328 -361
- package/src/ui-tailwind/tw-review-workspace.tsx +152 -286
|
@@ -28,13 +28,21 @@ export function buildPositionMap(surface: EditorSurfaceSnapshot): PositionMap {
|
|
|
28
28
|
return entries[entries.length - 1]?.pmEnd ?? pmDocSize - 1;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
let previous: MapEntry | null = null;
|
|
31
32
|
for (const entry of entries) {
|
|
33
|
+
if (
|
|
34
|
+
entry.runtimeStart === entry.runtimeEnd &&
|
|
35
|
+
runtimePos === entry.runtimeStart
|
|
36
|
+
) {
|
|
37
|
+
return entry.pmStart;
|
|
38
|
+
}
|
|
32
39
|
if (runtimePos >= entry.runtimeStart && runtimePos < entry.runtimeEnd) {
|
|
33
40
|
return entry.pmStart + (runtimePos - entry.runtimeStart);
|
|
34
41
|
}
|
|
35
42
|
if (runtimePos < entry.runtimeStart) {
|
|
36
|
-
return entry
|
|
43
|
+
return nearestRuntimeGapPm(runtimePos, previous, entry);
|
|
37
44
|
}
|
|
45
|
+
previous = entry;
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
return entries[entries.length - 1]?.pmEnd ?? 1;
|
|
@@ -48,13 +56,15 @@ export function buildPositionMap(surface: EditorSurfaceSnapshot): PositionMap {
|
|
|
48
56
|
return runtimeStorySize;
|
|
49
57
|
}
|
|
50
58
|
|
|
59
|
+
let previous: MapEntry | null = null;
|
|
51
60
|
for (const entry of entries) {
|
|
52
61
|
if (pmPos >= entry.pmStart && pmPos <= entry.pmEnd) {
|
|
53
62
|
return entry.runtimeStart + (pmPos - entry.pmStart);
|
|
54
63
|
}
|
|
55
64
|
if (pmPos < entry.pmStart) {
|
|
56
|
-
return entry
|
|
65
|
+
return nearestPmGapRuntime(pmPos, previous, entry);
|
|
57
66
|
}
|
|
67
|
+
previous = entry;
|
|
58
68
|
}
|
|
59
69
|
|
|
60
70
|
return runtimeStorySize;
|
|
@@ -65,6 +75,32 @@ export function buildPositionMap(surface: EditorSurfaceSnapshot): PositionMap {
|
|
|
65
75
|
};
|
|
66
76
|
}
|
|
67
77
|
|
|
78
|
+
function nearestRuntimeGapPm(
|
|
79
|
+
runtimePos: number,
|
|
80
|
+
previous: MapEntry | null,
|
|
81
|
+
next: MapEntry,
|
|
82
|
+
): number {
|
|
83
|
+
if (!previous) {
|
|
84
|
+
return next.pmStart;
|
|
85
|
+
}
|
|
86
|
+
const distanceToPrevious = Math.abs(runtimePos - previous.runtimeEnd);
|
|
87
|
+
const distanceToNext = Math.abs(next.runtimeStart - runtimePos);
|
|
88
|
+
return distanceToPrevious <= distanceToNext ? previous.pmEnd : next.pmStart;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function nearestPmGapRuntime(
|
|
92
|
+
pmPos: number,
|
|
93
|
+
previous: MapEntry | null,
|
|
94
|
+
next: MapEntry,
|
|
95
|
+
): number {
|
|
96
|
+
if (!previous) {
|
|
97
|
+
return next.runtimeStart;
|
|
98
|
+
}
|
|
99
|
+
const distanceToPrevious = Math.abs(pmPos - previous.pmEnd);
|
|
100
|
+
const distanceToNext = Math.abs(next.pmStart - pmPos);
|
|
101
|
+
return distanceToPrevious <= distanceToNext ? previous.runtimeEnd : next.runtimeStart;
|
|
102
|
+
}
|
|
103
|
+
|
|
68
104
|
function walkBlocks(
|
|
69
105
|
blocks: SurfaceBlockSnapshot[],
|
|
70
106
|
pmCursor: number,
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
tableRowNodeSpec,
|
|
5
5
|
tableCellNodeSpec,
|
|
6
6
|
tableHeaderCellNodeSpec,
|
|
7
|
-
} from "../../
|
|
7
|
+
} from "../../api/table-node-specs.ts";
|
|
8
8
|
import {
|
|
9
9
|
isSupportedShapeGeometry,
|
|
10
10
|
renderShapeSvg,
|
|
@@ -199,6 +199,7 @@ export const editorSchema = new Schema({
|
|
|
199
199
|
bidi: { default: null },
|
|
200
200
|
pageBreakBefore: { default: null },
|
|
201
201
|
hiddenTextOnly: { default: null },
|
|
202
|
+
tabLayout: { default: null },
|
|
202
203
|
placeholderCulled: { default: null },
|
|
203
204
|
/**
|
|
204
205
|
* Rendered height (in twips) of the block that this placeholder
|
|
@@ -541,11 +542,17 @@ export const editorSchema = new Schema({
|
|
|
541
542
|
: (numberingPrefix ?? ""),
|
|
542
543
|
]);
|
|
543
544
|
}
|
|
545
|
+
const contentAttrs: Record<string, string> = {
|
|
546
|
+
class: "pm-paragraph-content",
|
|
547
|
+
};
|
|
548
|
+
if (node.attrs.tabLayout === "right") {
|
|
549
|
+
contentAttrs["data-tab-layout"] = "right";
|
|
550
|
+
contentAttrs.style =
|
|
551
|
+
"display: inline-flex; align-items: baseline; width: 100%; min-width: 0; white-space: nowrap";
|
|
552
|
+
}
|
|
544
553
|
children.push([
|
|
545
554
|
"span",
|
|
546
|
-
|
|
547
|
-
class: "pm-paragraph-content",
|
|
548
|
-
},
|
|
555
|
+
contentAttrs,
|
|
549
556
|
0,
|
|
550
557
|
]);
|
|
551
558
|
return ["p", attrs, ...children];
|
|
@@ -585,6 +592,9 @@ export const editorSchema = new Schema({
|
|
|
585
592
|
`width: ${width && width > 0 ? width : 32}px`,
|
|
586
593
|
`min-width: 8px`,
|
|
587
594
|
];
|
|
595
|
+
if (align === "right" || align === "end") {
|
|
596
|
+
styles.push(`flex: 1 1 auto`);
|
|
597
|
+
}
|
|
588
598
|
if (leader === "dot" || leader === "middleDot") {
|
|
589
599
|
styles.push(
|
|
590
600
|
`background-image: radial-gradient(circle, currentColor 1px, transparent 1.25px)`,
|
|
@@ -343,8 +343,14 @@ export function createPMSelectionFromSnapshot(
|
|
|
343
343
|
);
|
|
344
344
|
} catch {
|
|
345
345
|
// If the mapped runtime selection is invalid or lands in a non-text block,
|
|
346
|
-
//
|
|
347
|
-
|
|
346
|
+
// keep the cursor near the mapped anchor. Falling back to doc start makes
|
|
347
|
+
// table-boundary mapping failures show up as visible cursor jumps.
|
|
348
|
+
try {
|
|
349
|
+
const safeAnchor = clamp(pmAnchor, 0, doc.content.size);
|
|
350
|
+
return Selection.near(doc.resolve(safeAnchor), selection.head >= selection.anchor ? 1 : -1);
|
|
351
|
+
} catch {
|
|
352
|
+
return Selection.atStart(doc);
|
|
353
|
+
}
|
|
348
354
|
}
|
|
349
355
|
}
|
|
350
356
|
|
|
@@ -512,7 +518,7 @@ function buildParagraph(
|
|
|
512
518
|
: 0;
|
|
513
519
|
const widthPx = Math.round((stopPos - prevPos) / 15);
|
|
514
520
|
const leader = (stop as { leader?: string }).leader ?? null;
|
|
515
|
-
const align = (stop as { val?: string }).val ?? null;
|
|
521
|
+
const align = (stop as { val?: string; align?: string }).val ?? (stop as { align?: string }).align ?? null;
|
|
516
522
|
content.push(
|
|
517
523
|
editorSchema.nodes.tab_char.create({
|
|
518
524
|
tabWidth: widthPx > 8 ? widthPx : null,
|
|
@@ -594,6 +600,7 @@ function buildParagraph(
|
|
|
594
600
|
bidi: block.bidi ?? cascade?.bidi ?? null,
|
|
595
601
|
pageBreakBefore: block.pageBreakBefore ?? cascade?.pageBreakBefore ?? null,
|
|
596
602
|
hiddenTextOnly: fullyVanishedParagraph || null,
|
|
603
|
+
tabLayout: hasRightAlignedTabStop(block, paragraphLayout.tabStops) ? "right" : null,
|
|
597
604
|
// `<w:framePr>` out-of-flow frame — forward to the PM paragraph node
|
|
598
605
|
// so `pm-schema.ts::paragraph.toDOM` emits the absolute positioning
|
|
599
606
|
// that matches the static `buildParagraphStyle` path (L04
|
|
@@ -605,12 +612,30 @@ function buildParagraph(
|
|
|
605
612
|
);
|
|
606
613
|
}
|
|
607
614
|
|
|
615
|
+
function readTabStopAlign(stop: { val?: string; align?: string }): string | undefined {
|
|
616
|
+
return stop.val ?? stop.align;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function hasRightAlignedTabStop(
|
|
620
|
+
block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
621
|
+
tabStops: Array<{ val?: string; align?: string }>,
|
|
622
|
+
): boolean {
|
|
623
|
+
let tabIndex = 0;
|
|
624
|
+
for (const segment of block.segments) {
|
|
625
|
+
if (segment.kind !== "tab") continue;
|
|
626
|
+
const align = readTabStopAlign(tabStops[tabIndex] ?? {});
|
|
627
|
+
if (align === "right" || align === "end") return true;
|
|
628
|
+
tabIndex += 1;
|
|
629
|
+
}
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
|
|
608
633
|
function resolveParagraphLayout(
|
|
609
634
|
block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
610
635
|
): {
|
|
611
636
|
spacing: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>["spacing"];
|
|
612
637
|
indentation: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>["indentation"];
|
|
613
|
-
tabStops: Array<{ pos
|
|
638
|
+
tabStops: Array<{ pos?: number; position?: number; val?: string; align?: string; leader?: string }>;
|
|
614
639
|
markerLane: NonNullable<
|
|
615
640
|
NonNullable<Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>["resolvedNumbering"]>["geometry"]["markerLane"]
|
|
616
641
|
> | undefined;
|
|
@@ -628,7 +653,11 @@ function resolveParagraphLayout(
|
|
|
628
653
|
block.resolvedNumbering?.geometry.indentation ??
|
|
629
654
|
block.indentation ??
|
|
630
655
|
cascadeFormatting?.indentation,
|
|
631
|
-
tabStops:
|
|
656
|
+
tabStops:
|
|
657
|
+
block.resolvedNumbering?.geometry.tabStops ??
|
|
658
|
+
block.tabStops ??
|
|
659
|
+
cascadeFormatting?.tabStops ??
|
|
660
|
+
[],
|
|
632
661
|
markerLane: block.resolvedNumbering?.geometry.markerLane,
|
|
633
662
|
markerJustification: block.resolvedNumbering?.geometry.markerJustification,
|
|
634
663
|
};
|
|
@@ -51,7 +51,8 @@ import type {
|
|
|
51
51
|
RevisionDecorationModel,
|
|
52
52
|
RevisionDisplayFlags,
|
|
53
53
|
} from "../../ui/headless/revision-decoration-model";
|
|
54
|
-
import {
|
|
54
|
+
import { buildRuntimeDecorations } from "./pm-decorations";
|
|
55
|
+
import { recordPerfSample } from "./perf-probe";
|
|
55
56
|
import type { PositionMap } from "./pm-position-map";
|
|
56
57
|
|
|
57
58
|
/** Inputs that drive every call to `buildDecorations`, plus an opaque
|
|
@@ -116,24 +117,7 @@ export function __resetRuntimeDecorationCountersForTests(): void {
|
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
function rebuildSet(doc: PMNode, inputs: RuntimeDecorationInputs): DecorationSet {
|
|
119
|
-
const base =
|
|
120
|
-
doc,
|
|
121
|
-
inputs.positionMap,
|
|
122
|
-
inputs.commentModel,
|
|
123
|
-
inputs.revisionModel,
|
|
124
|
-
inputs.markupDisplay,
|
|
125
|
-
inputs.showTrackedChanges,
|
|
126
|
-
inputs.suggestionsEnabled,
|
|
127
|
-
inputs.workflowScopes,
|
|
128
|
-
inputs.activeStory,
|
|
129
|
-
inputs.workflowCandidates,
|
|
130
|
-
inputs.workflowBlockedReasons,
|
|
131
|
-
inputs.workflowLockedZones,
|
|
132
|
-
inputs.activeWorkflowWorkItemId,
|
|
133
|
-
inputs.activeWorkflowScopeIds,
|
|
134
|
-
inputs.workflowMetadata,
|
|
135
|
-
inputs.revisionDisplayByOffset,
|
|
136
|
-
);
|
|
120
|
+
const base = buildRuntimeDecorations({ doc, ...inputs });
|
|
137
121
|
const extras = inputs.extraDecorations ? inputs.extraDecorations(doc) : [];
|
|
138
122
|
if (extras.length === 0) return base;
|
|
139
123
|
// Layer extras on top of the base set. `DecorationSet.add` is the
|
|
@@ -186,5 +170,11 @@ export function applyRuntimeDecorationInputs(
|
|
|
186
170
|
const tr: Transaction = view.state.tr.setMeta(runtimeDecorationPluginKey, {
|
|
187
171
|
inputs,
|
|
188
172
|
});
|
|
173
|
+
const startedAt = nowMs();
|
|
189
174
|
view.dispatch(tr);
|
|
175
|
+
recordPerfSample("pm.decorations.apply", nowMs() - startedAt);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function nowMs(): number {
|
|
179
|
+
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
190
180
|
}
|
|
@@ -21,7 +21,7 @@ export function createSurfaceDocumentBuildKey(input: {
|
|
|
21
21
|
surface: EditorSurfaceSnapshot | null | undefined;
|
|
22
22
|
activeStory: EditorStoryTarget;
|
|
23
23
|
mediaPreviewKey: string;
|
|
24
|
-
|
|
24
|
+
unsupportedObjectPreviewsVisible?: boolean;
|
|
25
25
|
isPageWorkspace?: boolean;
|
|
26
26
|
}): string {
|
|
27
27
|
const ranges = input.surface?.viewportBlockRanges ?? null;
|
|
@@ -32,7 +32,7 @@ export function createSurfaceDocumentBuildKey(input: {
|
|
|
32
32
|
: getSurfaceIdentity(input.surface),
|
|
33
33
|
activeStory: input.activeStory,
|
|
34
34
|
mediaPreviewKey: input.mediaPreviewKey,
|
|
35
|
-
|
|
35
|
+
unsupportedObjectPreviewsVisible: input.unsupportedObjectPreviewsVisible ?? false,
|
|
36
36
|
isPageWorkspace: input.isPageWorkspace ?? false,
|
|
37
37
|
// Serialize all intervals (sorted by start) — disjoint viewport+caret
|
|
38
38
|
// ranges must key distinctly from a single merged range so PM rebuilds
|
|
@@ -136,6 +136,151 @@ export function computeTabWidthsInPoints(
|
|
|
136
136
|
return widths;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
export interface TabRenderInfo {
|
|
140
|
+
widthPt: number;
|
|
141
|
+
align?: string;
|
|
142
|
+
leader?: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function readTabStop(
|
|
146
|
+
stop: {
|
|
147
|
+
pos?: number;
|
|
148
|
+
position?: number;
|
|
149
|
+
val?: string;
|
|
150
|
+
align?: string;
|
|
151
|
+
leader?: string;
|
|
152
|
+
},
|
|
153
|
+
): { pos: number; align?: string; leader?: string } {
|
|
154
|
+
return {
|
|
155
|
+
pos: stop.pos ?? stop.position ?? 0,
|
|
156
|
+
align: stop.val ?? stop.align,
|
|
157
|
+
leader: stop.leader,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function resolveTabStops(
|
|
162
|
+
block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
163
|
+
):
|
|
164
|
+
| Array<{
|
|
165
|
+
pos?: number;
|
|
166
|
+
position?: number;
|
|
167
|
+
val?: string;
|
|
168
|
+
align?: string;
|
|
169
|
+
leader?: string;
|
|
170
|
+
}>
|
|
171
|
+
| undefined {
|
|
172
|
+
return (
|
|
173
|
+
block.resolvedNumbering?.geometry.tabStops ??
|
|
174
|
+
block.tabStops ??
|
|
175
|
+
block.resolvedParagraphFormatting?.tabStops
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function isRightAlignedTab(align: string | undefined): boolean {
|
|
180
|
+
return align === "right" || align === "end";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function hasRightAlignedTabStop(
|
|
184
|
+
block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
185
|
+
): boolean {
|
|
186
|
+
const rawStops = resolveTabStops(block);
|
|
187
|
+
if (!rawStops || rawStops.length === 0) return false;
|
|
188
|
+
|
|
189
|
+
let tabIndex = 0;
|
|
190
|
+
for (const seg of block.segments) {
|
|
191
|
+
if (seg.kind !== "tab") continue;
|
|
192
|
+
const stop = rawStops[tabIndex];
|
|
193
|
+
if (stop && isRightAlignedTab(readTabStop(stop).align)) return true;
|
|
194
|
+
tabIndex += 1;
|
|
195
|
+
}
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function buildParagraphContentStyle(
|
|
200
|
+
block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
201
|
+
): React.CSSProperties | undefined {
|
|
202
|
+
if (!hasRightAlignedTabStop(block)) return undefined;
|
|
203
|
+
return {
|
|
204
|
+
display: "inline-flex",
|
|
205
|
+
alignItems: "baseline",
|
|
206
|
+
width: "100%",
|
|
207
|
+
minWidth: 0,
|
|
208
|
+
whiteSpace: "nowrap",
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Precompute per-tab render data from paragraph tab stops.
|
|
214
|
+
*
|
|
215
|
+
* Mirrors `computeTabWidthsInPoints`, but keeps the tab stop's alignment and
|
|
216
|
+
* leader metadata so static body and region renderers can match the PM
|
|
217
|
+
* `tab_char.toDOM` path for TOC leader dots and related leader styles.
|
|
218
|
+
*/
|
|
219
|
+
export function computeTabRenderInfoBySegment(
|
|
220
|
+
block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
221
|
+
): Map<string, TabRenderInfo> {
|
|
222
|
+
const infos = new Map<string, TabRenderInfo>();
|
|
223
|
+
const rawStops = resolveTabStops(block);
|
|
224
|
+
if (!rawStops || rawStops.length === 0) return infos;
|
|
225
|
+
|
|
226
|
+
let tabIndex = 0;
|
|
227
|
+
for (const seg of block.segments) {
|
|
228
|
+
if (seg.kind !== "tab") continue;
|
|
229
|
+
const stop = rawStops[tabIndex];
|
|
230
|
+
if (stop) {
|
|
231
|
+
const current = readTabStop(stop);
|
|
232
|
+
const prevPos =
|
|
233
|
+
tabIndex > 0 ? readTabStop(rawStops[tabIndex - 1]!).pos : 0;
|
|
234
|
+
const widthTwips = current.pos - prevPos;
|
|
235
|
+
if (widthTwips > 0) {
|
|
236
|
+
infos.set(seg.segmentId, {
|
|
237
|
+
widthPt: widthTwips / 20,
|
|
238
|
+
...(current.align ? { align: current.align } : {}),
|
|
239
|
+
...(current.leader ? { leader: current.leader } : {}),
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
tabIndex += 1;
|
|
244
|
+
}
|
|
245
|
+
return infos;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function buildTabStyle(
|
|
249
|
+
info: TabRenderInfo | undefined,
|
|
250
|
+
): React.CSSProperties {
|
|
251
|
+
const style: React.CSSProperties =
|
|
252
|
+
typeof info?.widthPt === "number"
|
|
253
|
+
? { display: "inline-block", width: `${info.widthPt}pt`, minWidth: "8px" }
|
|
254
|
+
: { display: "inline-block", width: "32px", minWidth: "8px" };
|
|
255
|
+
|
|
256
|
+
if (isRightAlignedTab(info?.align)) {
|
|
257
|
+
style.flex = "1 1 auto";
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
switch (info?.leader) {
|
|
261
|
+
case "dot":
|
|
262
|
+
case "middleDot":
|
|
263
|
+
style.backgroundImage =
|
|
264
|
+
"radial-gradient(circle, currentColor 1px, transparent 1.25px)";
|
|
265
|
+
style.backgroundSize = "6px 3px";
|
|
266
|
+
style.backgroundRepeat = "repeat-x";
|
|
267
|
+
style.backgroundPosition = "left calc(100% - 2px)";
|
|
268
|
+
style.opacity = 0.55;
|
|
269
|
+
break;
|
|
270
|
+
case "hyphen":
|
|
271
|
+
style.borderBottom = "1px dashed rgba(107,107,107,0.65)";
|
|
272
|
+
break;
|
|
273
|
+
case "underscore":
|
|
274
|
+
style.borderBottom = "1px solid rgba(107,107,107,0.65)";
|
|
275
|
+
break;
|
|
276
|
+
case "heavy":
|
|
277
|
+
style.borderBottom = "2px solid rgba(107,107,107,0.75)";
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return style;
|
|
282
|
+
}
|
|
283
|
+
|
|
139
284
|
/** Build CSSProperties for a paragraph block from spacing/indent/alignment. */
|
|
140
285
|
export function buildParagraphStyle(
|
|
141
286
|
block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
@@ -6,12 +6,15 @@ import type {
|
|
|
6
6
|
} from "../../api/public-types.ts";
|
|
7
7
|
import {
|
|
8
8
|
buildMarkerStyle,
|
|
9
|
+
buildParagraphContentStyle,
|
|
9
10
|
buildParagraphStyle,
|
|
10
11
|
buildSegmentStyle,
|
|
11
|
-
|
|
12
|
+
buildTabStyle,
|
|
13
|
+
computeTabRenderInfoBySegment,
|
|
12
14
|
hasStyleEntries,
|
|
13
15
|
headingClassList,
|
|
14
16
|
resolveHeadingLevel,
|
|
17
|
+
type TabRenderInfo,
|
|
15
18
|
} from "./tw-page-block-view.helpers.ts";
|
|
16
19
|
|
|
17
20
|
// ---------------------------------------------------------------------------
|
|
@@ -19,7 +22,7 @@ import {
|
|
|
19
22
|
// ---------------------------------------------------------------------------
|
|
20
23
|
|
|
21
24
|
/** Render a single inline segment. */
|
|
22
|
-
function renderSegment(seg: SurfaceInlineSegment,
|
|
25
|
+
function renderSegment(seg: SurfaceInlineSegment, tabInfoBySegment: Map<string, TabRenderInfo>): React.ReactNode {
|
|
23
26
|
switch (seg.kind) {
|
|
24
27
|
case "text": {
|
|
25
28
|
const style = buildSegmentStyle(seg.marks, seg.markAttrs);
|
|
@@ -34,16 +37,13 @@ function renderSegment(seg: SurfaceInlineSegment, tabWidthsPt: Map<string, numbe
|
|
|
34
37
|
);
|
|
35
38
|
}
|
|
36
39
|
case "tab": {
|
|
37
|
-
const
|
|
38
|
-
const tabStyle: React.CSSProperties =
|
|
39
|
-
typeof widthPt === "number"
|
|
40
|
-
? { display: "inline-block", width: `${widthPt}pt`, minWidth: "8px" }
|
|
41
|
-
: { display: "inline-block", width: "32px", minWidth: "8px" };
|
|
40
|
+
const tabInfo = tabInfoBySegment.get(seg.segmentId);
|
|
42
41
|
return (
|
|
43
42
|
<span
|
|
44
43
|
key={seg.segmentId}
|
|
45
44
|
data-node-type="tab"
|
|
46
|
-
style={
|
|
45
|
+
style={buildTabStyle(tabInfo)}
|
|
46
|
+
title={tabInfo?.align ? `Tab stop · ${tabInfo.align}` : "Tab stop"}
|
|
47
47
|
>
|
|
48
48
|
{"\u00A0"}
|
|
49
49
|
</span>
|
|
@@ -121,7 +121,8 @@ function ParagraphBlock({
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
const pStyle = buildParagraphStyle(block);
|
|
124
|
-
const
|
|
124
|
+
const tabInfoBySegment = computeTabRenderInfoBySegment(block);
|
|
125
|
+
const contentStyle = buildParagraphContentStyle(block);
|
|
125
126
|
const attrs: React.HTMLAttributes<HTMLParagraphElement> & {
|
|
126
127
|
"data-heading-level"?: string;
|
|
127
128
|
"data-numbered"?: string;
|
|
@@ -199,8 +200,12 @@ function ParagraphBlock({
|
|
|
199
200
|
return (
|
|
200
201
|
<p {...attrs}>
|
|
201
202
|
{prefixSpan}
|
|
202
|
-
<span
|
|
203
|
-
|
|
203
|
+
<span
|
|
204
|
+
className="pm-paragraph-content"
|
|
205
|
+
data-tab-layout={contentStyle ? "right" : undefined}
|
|
206
|
+
style={contentStyle}
|
|
207
|
+
>
|
|
208
|
+
{block.segments.map((seg) => renderSegment(seg, tabInfoBySegment))}
|
|
204
209
|
</span>
|
|
205
210
|
</p>
|
|
206
211
|
);
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
DocumentNavigationSnapshot,
|
|
14
14
|
EditorStoryTarget,
|
|
15
15
|
EditorUser,
|
|
16
|
+
GeometryFacet,
|
|
16
17
|
RuntimeRenderSnapshot,
|
|
17
18
|
SearchOptions,
|
|
18
19
|
SearchResultSnapshot,
|
|
@@ -105,13 +106,12 @@ const BOUNDED_TYPING_SCROLL_RESTORE_MAX_DELTA_PX = 256;
|
|
|
105
106
|
export function shouldPreserveScrollAnchorForRebuild(options: {
|
|
106
107
|
policy: RebuildScrollAnchorPolicy | null;
|
|
107
108
|
view: Pick<EditorView, "hasFocus"> | null;
|
|
108
|
-
geometryFacet?:
|
|
109
|
+
geometryFacet?: GeometryFacet;
|
|
109
110
|
previousStory: EditorStoryTarget | null;
|
|
110
111
|
nextStory: EditorStoryTarget;
|
|
111
112
|
}): boolean {
|
|
112
113
|
if (options.policy !== "bounded-same-story") return false;
|
|
113
114
|
if (!options.view?.hasFocus()) return false;
|
|
114
|
-
if (!options.geometryFacet) return false;
|
|
115
115
|
if (!options.previousStory) return false;
|
|
116
116
|
return storyTargetsEqual(options.previousStory, options.nextStory);
|
|
117
117
|
}
|
|
@@ -128,9 +128,7 @@ export function shouldPreserveScrollAnchorForRebuild(options: {
|
|
|
128
128
|
* between adjacent blocks.
|
|
129
129
|
*/
|
|
130
130
|
function buildPageBreakDecorationsFromProps(
|
|
131
|
-
geometryFacet:
|
|
132
|
-
| import("../../runtime/geometry/index.ts").GeometryFacet
|
|
133
|
-
| undefined,
|
|
131
|
+
geometryFacet: GeometryFacet | undefined,
|
|
134
132
|
isMainStory: boolean,
|
|
135
133
|
positionMap: PositionMap,
|
|
136
134
|
posture: "canvas" | "page",
|
|
@@ -232,7 +230,7 @@ export interface TwProseMirrorSurfaceProps {
|
|
|
232
230
|
documentNavigation: DocumentNavigationSnapshot;
|
|
233
231
|
reviewMode: "editing" | "review";
|
|
234
232
|
markupDisplay: MarkupDisplay;
|
|
235
|
-
|
|
233
|
+
unsupportedObjectPreviewsVisible?: boolean;
|
|
236
234
|
activeRevisionId?: string;
|
|
237
235
|
activeSelectionToolKind?: ActiveSelectionToolModel["kind"] | null;
|
|
238
236
|
showTrackedChanges?: boolean;
|
|
@@ -340,7 +338,7 @@ export interface TwProseMirrorSurfaceProps {
|
|
|
340
338
|
* render-frame access; layout-facet's `getRenderFrame` is being
|
|
341
339
|
* deleted.
|
|
342
340
|
*/
|
|
343
|
-
geometryFacet?:
|
|
341
|
+
geometryFacet?: GeometryFacet;
|
|
344
342
|
/** Height in px of each page's header band. Default 32. */
|
|
345
343
|
pageChromeHeaderBandPx?: number;
|
|
346
344
|
/** Height in px of each page's footer band. Default 32. */
|
|
@@ -544,13 +542,13 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
544
542
|
surface,
|
|
545
543
|
activeStory: snapshot.activeStory,
|
|
546
544
|
mediaPreviewKey,
|
|
547
|
-
|
|
545
|
+
unsupportedObjectPreviewsVisible: props.unsupportedObjectPreviewsVisible,
|
|
548
546
|
isPageWorkspace: props.isPageWorkspace,
|
|
549
547
|
}),
|
|
550
548
|
[
|
|
551
549
|
mediaPreviewKey,
|
|
552
550
|
props.isPageWorkspace,
|
|
553
|
-
props.
|
|
551
|
+
props.unsupportedObjectPreviewsVisible,
|
|
554
552
|
snapshot.activeStory,
|
|
555
553
|
surface,
|
|
556
554
|
],
|
|
@@ -924,7 +922,7 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
924
922
|
snapshot.selection,
|
|
925
923
|
plugins,
|
|
926
924
|
props.mediaPreviews,
|
|
927
|
-
props.
|
|
925
|
+
props.unsupportedObjectPreviewsVisible ?? false,
|
|
928
926
|
props.isPageWorkspace,
|
|
929
927
|
);
|
|
930
928
|
positionMapRef.current = positionMap;
|
|
@@ -69,6 +69,9 @@ function createPaddingCell(colSpan: number, widthTwips: number): HTMLTableCellEl
|
|
|
69
69
|
const cell = document.createElement("td");
|
|
70
70
|
cell.setAttribute("data-row-padding", "true");
|
|
71
71
|
cell.setAttribute("aria-hidden", "true");
|
|
72
|
+
cell.setAttribute("contenteditable", "false");
|
|
73
|
+
cell.setAttribute("role", "presentation");
|
|
74
|
+
cell.tabIndex = -1;
|
|
72
75
|
cell.colSpan = Math.max(1, colSpan);
|
|
73
76
|
cell.style.border = "none";
|
|
74
77
|
cell.style.padding = "0";
|
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
SurfaceTableRowSnapshot,
|
|
21
21
|
WordReviewEditorLayoutFacet,
|
|
22
22
|
} from "../../api/public-types.ts";
|
|
23
|
+
import { buildPageAnchorAttributes } from "../../api/v3/_page-anchor-id.ts";
|
|
23
24
|
import type { PageOverlayRect } from "../chrome-overlay/tw-page-stack-overlay-layer.tsx";
|
|
24
25
|
import { TwTableContinuationHeader } from "../chrome-overlay/tw-table-continuation-header.tsx";
|
|
25
26
|
import { FRAME_PX_PER_TWIP_AT_96DPI } from "../tw-review-workspace.tsx";
|
|
@@ -144,6 +145,7 @@ function TwPageChromeEntryInner({
|
|
|
144
145
|
);
|
|
145
146
|
|
|
146
147
|
const frameHeightPx = rect.bottomPx - rect.topPx;
|
|
148
|
+
const pageAnchorAttributes = buildPageAnchorAttributes(rect.pageId, pageIndex);
|
|
147
149
|
|
|
148
150
|
// Viewport cull — lightweight placeholder outside the visible range.
|
|
149
151
|
const isCulled =
|
|
@@ -154,8 +156,8 @@ function TwPageChromeEntryInner({
|
|
|
154
156
|
if (isCulled) {
|
|
155
157
|
return (
|
|
156
158
|
<div
|
|
159
|
+
{...pageAnchorAttributes}
|
|
157
160
|
data-page-chrome-frame=""
|
|
158
|
-
data-page-index={pageIndex}
|
|
159
161
|
data-page-chrome-culled=""
|
|
160
162
|
style={{
|
|
161
163
|
position: "absolute",
|
|
@@ -190,8 +192,8 @@ function TwPageChromeEntryInner({
|
|
|
190
192
|
|
|
191
193
|
return (
|
|
192
194
|
<div
|
|
195
|
+
{...pageAnchorAttributes}
|
|
193
196
|
data-page-chrome-frame=""
|
|
194
|
-
data-page-index={pageIndex}
|
|
195
197
|
style={{
|
|
196
198
|
position: "absolute",
|
|
197
199
|
top: `${rect.topPx}px`,
|