@beyondwork/docx-react-component 1.0.52 → 1.0.54
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 +31 -40
- package/src/api/public-types.ts +67 -7
- package/src/io/chart-preview-resolver.ts +41 -0
- package/src/io/docx-session.ts +217 -23
- package/src/runtime/collab/checkpoint-store.ts +1 -1
- package/src/runtime/collab/event-types.ts +4 -0
- package/src/runtime/collab/runtime-collab-sync.ts +88 -8
- package/src/runtime/document-runtime.ts +182 -9
- package/src/runtime/layout/inert-layout-facet.ts +1 -0
- package/src/runtime/layout/layout-engine-version.ts +97 -2
- package/src/runtime/layout/layout-invalidation.ts +150 -30
- package/src/runtime/layout/page-graph.ts +19 -0
- package/src/runtime/layout/paginated-layout-engine.ts +128 -19
- package/src/runtime/layout/project-block-fragments.ts +27 -0
- package/src/runtime/layout/public-facet.ts +70 -1
- package/src/runtime/prerender/cache-envelope.ts +30 -0
- package/src/runtime/prerender/customxml-cache.ts +17 -3
- package/src/runtime/prerender/prerender-document.ts +17 -1
- package/src/runtime/render/render-frame-diff.ts +38 -2
- package/src/runtime/render/render-kernel.ts +67 -19
- package/src/runtime/surface-projection.ts +28 -0
- package/src/runtime/table-schema.ts +27 -0
- package/src/runtime/table-style-resolver.ts +51 -0
- package/src/ui/WordReviewEditor.tsx +6 -3
- package/src/ui/editor-runtime-boundary.ts +39 -2
- package/src/ui/headless/comment-decoration-model.ts +60 -5
- package/src/ui/headless/revision-decoration-model.ts +94 -6
- package/src/ui/shared/revision-filters.ts +16 -6
- package/src/ui-tailwind/chart/ChartSurface.tsx +236 -0
- package/src/ui-tailwind/chart/layout/axis-layout.ts +17 -9
- package/src/ui-tailwind/chart/layout/legend-layout.ts +231 -0
- package/src/ui-tailwind/chart/layout/plot-area.ts +152 -59
- package/src/ui-tailwind/chart/layout/title-layout.ts +184 -0
- package/src/ui-tailwind/chart/render/area.tsx +277 -0
- package/src/ui-tailwind/chart/render/bar-column.tsx +356 -0
- package/src/ui-tailwind/chart/render/bubble.tsx +134 -0
- package/src/ui-tailwind/chart/render/combo.tsx +85 -0
- package/src/ui-tailwind/chart/render/data-labels.tsx +513 -0
- package/src/ui-tailwind/chart/render/font-metrics.ts +298 -0
- package/src/ui-tailwind/chart/render/gridlines.ts +228 -0
- package/src/ui-tailwind/chart/render/line.tsx +363 -0
- package/src/ui-tailwind/chart/render/number-format.ts +120 -16
- package/src/ui-tailwind/chart/render/pie.tsx +275 -0
- package/src/ui-tailwind/chart/render/progressive-render.ts +103 -0
- package/src/ui-tailwind/chart/render/scatter.tsx +228 -0
- package/src/ui-tailwind/chart/render/smooth-curve.ts +101 -0
- package/src/ui-tailwind/chart/render/svg-primitives.ts +378 -0
- package/src/ui-tailwind/chart/render/unsupported.tsx +126 -0
- package/src/ui-tailwind/chrome/collab-audience-chip.tsx +11 -0
- package/src/ui-tailwind/chrome/collab-negotiation-action-bar.tsx +44 -18
- package/src/ui-tailwind/chrome/collab-presence-strip.tsx +68 -7
- package/src/ui-tailwind/chrome/collab-role-chip.tsx +21 -2
- package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +20 -3
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +102 -37
- package/src/ui-tailwind/chrome/tw-command-palette.tsx +358 -0
- package/src/ui-tailwind/chrome/tw-comment-preview.tsx +108 -0
- package/src/ui-tailwind/chrome/tw-context-menu.tsx +227 -0
- package/src/ui-tailwind/chrome/tw-display-mode-selector.tsx +136 -0
- package/src/ui-tailwind/chrome/tw-empty-state.tsx +76 -0
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +30 -16
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +23 -4
- package/src/ui-tailwind/chrome/tw-paste-drop-toast.tsx +113 -0
- package/src/ui-tailwind/chrome/tw-revision-hover-preview.tsx +150 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-formatting.tsx +2 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +38 -2
- package/src/ui-tailwind/chrome/tw-selection-tool-placement.ts +15 -3
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +32 -20
- package/src/ui-tailwind/chrome/tw-shortcut-hint.tsx +68 -0
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +10 -10
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +26 -5
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +29 -22
- package/src/ui-tailwind/chrome/tw-unsaved-modal.tsx +72 -10
- package/src/ui-tailwind/chrome-overlay/tw-scope-card.tsx +33 -18
- package/src/ui-tailwind/chrome-overlay/tw-table-continuation-header.tsx +94 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +1 -0
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +20 -7
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +54 -0
- package/src/ui-tailwind/editor-surface/scroll-anchor.ts +93 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +107 -3
- package/src/ui-tailwind/index.ts +11 -0
- package/src/ui-tailwind/page-stack/tw-footnote-area.tsx +2 -2
- package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +274 -0
- package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +15 -2
- package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +15 -2
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +19 -147
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +83 -32
- package/src/ui-tailwind/review/tw-health-panel.tsx +174 -109
- package/src/ui-tailwind/review/tw-rail-card.tsx +9 -1
- package/src/ui-tailwind/review/tw-review-rail.tsx +36 -42
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +189 -101
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +11 -1
- package/src/ui-tailwind/status/tw-status-bar.tsx +114 -46
- package/src/ui-tailwind/theme/chart-palette-adapter.ts +57 -0
- package/src/ui-tailwind/theme/editor-theme.css +275 -46
- package/src/ui-tailwind/theme/tokens.css +345 -0
- package/src/ui-tailwind/theme/tokens.ts +313 -0
- package/src/ui-tailwind/theme/use-density.ts +60 -0
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +14 -1
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +73 -32
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +49 -9
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +178 -14
- package/src/ui-tailwind/tw-review-workspace.tsx +39 -6
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +0 -85
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beyondwork/docx-react-component",
|
|
3
3
|
"publisher": "beyondwork",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.54",
|
|
5
5
|
"description": "Embeddable React Word (docx) editor with review, comments, tracked changes, and round-trip OOXML fidelity.",
|
|
6
|
-
"packageManager": "pnpm@10.30.3",
|
|
7
6
|
"type": "module",
|
|
8
7
|
"sideEffects": [
|
|
9
8
|
"**/*.css"
|
|
@@ -93,35 +92,6 @@
|
|
|
93
92
|
"./ui-tailwind/theme/editor-theme.css": "./src/ui-tailwind/theme/editor-theme.css"
|
|
94
93
|
},
|
|
95
94
|
"types": "./src/index.ts",
|
|
96
|
-
"scripts": {
|
|
97
|
-
"build": "tsup",
|
|
98
|
-
"test": "bash scripts/run-workspace-tests.sh",
|
|
99
|
-
"test:repo": "node scripts/ci-check-layout-engine-version.mjs && node scripts/run-repo-tests.mjs core",
|
|
100
|
-
"test:repo:all": "node scripts/run-repo-tests.mjs all",
|
|
101
|
-
"test:repo:optional": "node scripts/run-repo-tests.mjs optional",
|
|
102
|
-
"test:repo:browser-ui": "node scripts/run-repo-tests.mjs browser-ui",
|
|
103
|
-
"test:wcag-audit": "node scripts/run-repo-tests.mjs wcag-audit",
|
|
104
|
-
"test:harness": "pnpm --filter @docx-react-component/react-word-editor-harness test",
|
|
105
|
-
"test:visual": "VISUAL_SMOKE_PROFILE=bare pnpm exec playwright test --project=chromium",
|
|
106
|
-
"test:visual:chrome": "VISUAL_SMOKE_PROFILE=chrome-cycle pnpm exec playwright test --project=chromium",
|
|
107
|
-
"visual:list-runs": "node scripts/visual-smoke-list-runs.mjs",
|
|
108
|
-
"mcp:visual-smoke": "node scripts/visual-smoke-mcp.mjs",
|
|
109
|
-
"lint": "pnpm run lint:no-authored-js && pnpm run lint:docs-contracts && pnpm run lint:tsgo && pnpm run lint:tsgo:harness",
|
|
110
|
-
"lint:docs-contracts": "bash scripts/check-reference-load-contract.sh",
|
|
111
|
-
"lint:no-authored-js": "bash scripts/check-no-authored-js.sh",
|
|
112
|
-
"lint:tsgo": "tsgo --noEmit -p tsconfig.build.json",
|
|
113
|
-
"lint:tsgo:harness": "pnpm --filter @docx-react-component/react-word-editor-harness lint:tsgo",
|
|
114
|
-
"context7:api-check": "bash scripts/context7-export-env.sh run bash scripts/context7-api-check.sh",
|
|
115
|
-
"wave:doctor": "bash scripts/context7-export-env.sh run pnpm exec wave doctor --json",
|
|
116
|
-
"wave:dry-run": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main --dry-run --no-dashboard",
|
|
117
|
-
"wave:launch": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main",
|
|
118
|
-
"wave:launch:managed": "bash scripts/wave-launch.sh",
|
|
119
|
-
"wave:status": "bash scripts/wave-status.sh",
|
|
120
|
-
"wave:watch": "bash scripts/wave-watch.sh --follow",
|
|
121
|
-
"wave:dashboard:current": "bash scripts/wave-dashboard-attach.sh current",
|
|
122
|
-
"wave:dashboard:global": "bash scripts/wave-dashboard-attach.sh global",
|
|
123
|
-
"harness:dev": "pnpm --filter @docx-react-component/react-word-editor-harness dev"
|
|
124
|
-
},
|
|
125
95
|
"keywords": [
|
|
126
96
|
"docx",
|
|
127
97
|
"word",
|
|
@@ -205,14 +175,35 @@
|
|
|
205
175
|
"y-protocols": "^1.0.7",
|
|
206
176
|
"yjs": "^13.6.30"
|
|
207
177
|
},
|
|
208
|
-
"
|
|
209
|
-
"
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
"
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
178
|
+
"scripts": {
|
|
179
|
+
"build": "tsup",
|
|
180
|
+
"test": "bash scripts/run-workspace-tests.sh",
|
|
181
|
+
"test:repo": "node scripts/ci-check-layout-engine-version.mjs && node scripts/run-repo-tests.mjs core",
|
|
182
|
+
"test:repo:all": "node scripts/run-repo-tests.mjs all",
|
|
183
|
+
"test:repo:optional": "node scripts/run-repo-tests.mjs optional",
|
|
184
|
+
"test:repo:browser-ui": "node scripts/run-repo-tests.mjs browser-ui",
|
|
185
|
+
"test:wcag-audit": "node scripts/run-repo-tests.mjs wcag-audit",
|
|
186
|
+
"test:harness": "pnpm --filter @docx-react-component/react-word-editor-harness test",
|
|
187
|
+
"test:visual": "VISUAL_SMOKE_PROFILE=bare pnpm exec playwright test --project=chromium",
|
|
188
|
+
"test:visual:chrome": "VISUAL_SMOKE_PROFILE=chrome-cycle pnpm exec playwright test --project=chromium",
|
|
189
|
+
"visual:list-runs": "node scripts/visual-smoke-list-runs.mjs",
|
|
190
|
+
"mcp:visual-smoke": "node scripts/visual-smoke-mcp.mjs",
|
|
191
|
+
"lint": "pnpm run lint:no-authored-js && pnpm run lint:docs-contracts && pnpm run lint:tsgo && pnpm run lint:tsgo:harness",
|
|
192
|
+
"lint:docs-contracts": "bash scripts/check-reference-load-contract.sh",
|
|
193
|
+
"lint:no-authored-js": "bash scripts/check-no-authored-js.sh",
|
|
194
|
+
"lint:tsgo": "tsgo --noEmit -p tsconfig.build.json",
|
|
195
|
+
"lint:tsgo:harness": "pnpm --filter @docx-react-component/react-word-editor-harness lint:tsgo",
|
|
196
|
+
"generate:token-reference": "node scripts/generate-token-reference.mjs",
|
|
197
|
+
"check:token-reference": "node scripts/generate-token-reference.mjs --check",
|
|
198
|
+
"context7:api-check": "bash scripts/context7-export-env.sh run bash scripts/context7-api-check.sh",
|
|
199
|
+
"wave:doctor": "bash scripts/context7-export-env.sh run pnpm exec wave doctor --json",
|
|
200
|
+
"wave:dry-run": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main --dry-run --no-dashboard",
|
|
201
|
+
"wave:launch": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main",
|
|
202
|
+
"wave:launch:managed": "bash scripts/wave-launch.sh",
|
|
203
|
+
"wave:status": "bash scripts/wave-status.sh",
|
|
204
|
+
"wave:watch": "bash scripts/wave-watch.sh --follow",
|
|
205
|
+
"wave:dashboard:current": "bash scripts/wave-dashboard-attach.sh current",
|
|
206
|
+
"wave:dashboard:global": "bash scripts/wave-dashboard-attach.sh global",
|
|
207
|
+
"harness:dev": "pnpm --filter @docx-react-component/react-word-editor-harness dev"
|
|
217
208
|
}
|
|
218
209
|
}
|
package/src/api/public-types.ts
CHANGED
|
@@ -892,6 +892,13 @@ export interface SurfaceTableCellSnapshot {
|
|
|
892
892
|
borderRight?: string | null;
|
|
893
893
|
borderBottom?: string | null;
|
|
894
894
|
borderLeft?: string | null;
|
|
895
|
+
/**
|
|
896
|
+
* R3.a Phase 2: per-cell text-flow direction copied from
|
|
897
|
+
* `TableCellNode.textDirection`. The node-view maps these to CSS
|
|
898
|
+
* `writing-mode` (`tbRl` → `vertical-rl`, `btLr` → `vertical-lr`, `lrTb` →
|
|
899
|
+
* unset / horizontal default). Cells with no direction stay horizontal.
|
|
900
|
+
*/
|
|
901
|
+
textDirection?: "lrTb" | "tbRl" | "btLr" | null;
|
|
895
902
|
/**
|
|
896
903
|
* R2a: space-joined CSS class names from the resolved table-style conditional
|
|
897
904
|
* regions (e.g. "band-firstRow band-band1Horz"). Consumers apply these to the
|
|
@@ -985,6 +992,31 @@ export type SurfaceBlockSnapshot =
|
|
|
985
992
|
noHBand?: boolean;
|
|
986
993
|
noVBand?: boolean;
|
|
987
994
|
};
|
|
995
|
+
/**
|
|
996
|
+
* R3.a Phase 2: resolved table-level properties (width / layoutMode /
|
|
997
|
+
* cellSpacing / borders) projected from the canonical TableNode + its
|
|
998
|
+
* resolved table-style cascade. Borders carry the typed `BorderSpec`
|
|
999
|
+
* per side; the node-view converts top/right/bottom/left to CSS
|
|
1000
|
+
* shorthand. insideH / insideV are stamped here for round-trip and
|
|
1001
|
+
* future renderer use, but at render time their visual effect is
|
|
1002
|
+
* realized through per-cell borders on the cell snapshot (CSS has no
|
|
1003
|
+
* direct "table-inside-border" property).
|
|
1004
|
+
*/
|
|
1005
|
+
tableResolved?: {
|
|
1006
|
+
width?: number | null;
|
|
1007
|
+
widthType?: "auto" | "dxa" | "pct" | "nil" | null;
|
|
1008
|
+
layoutMode?: "fixed" | "autofit" | null;
|
|
1009
|
+
cellSpacing?: number | null;
|
|
1010
|
+
cellSpacingType?: "auto" | "dxa" | "pct" | "nil" | null;
|
|
1011
|
+
borders?: {
|
|
1012
|
+
top?: { value?: string; size?: number; space?: number; color?: string } | null;
|
|
1013
|
+
right?: { value?: string; size?: number; space?: number; color?: string } | null;
|
|
1014
|
+
bottom?: { value?: string; size?: number; space?: number; color?: string } | null;
|
|
1015
|
+
left?: { value?: string; size?: number; space?: number; color?: string } | null;
|
|
1016
|
+
insideH?: { value?: string; size?: number; space?: number; color?: string } | null;
|
|
1017
|
+
insideV?: { value?: string; size?: number; space?: number; color?: string } | null;
|
|
1018
|
+
} | null;
|
|
1019
|
+
};
|
|
988
1020
|
rows: SurfaceTableRowSnapshot[];
|
|
989
1021
|
}
|
|
990
1022
|
| {
|
|
@@ -3026,12 +3058,16 @@ export interface WordReviewEditorRef {
|
|
|
3026
3058
|
setWorkflowOverlay(overlay: WorkflowOverlay): void;
|
|
3027
3059
|
clearWorkflowOverlay(): void;
|
|
3028
3060
|
/**
|
|
3029
|
-
*
|
|
3030
|
-
* overlay
|
|
3031
|
-
* `
|
|
3032
|
-
*
|
|
3033
|
-
*
|
|
3034
|
-
*
|
|
3061
|
+
* Return a structural clone of the currently-registered workflow
|
|
3062
|
+
* overlay, or `null` when no overlay has been set (or
|
|
3063
|
+
* `clearWorkflowOverlay` has been called). Intended for the canonical
|
|
3064
|
+
* host read-before-write pattern — read current, merge host-owned
|
|
3065
|
+
* fields (e.g. `candidates`), write back via `setWorkflowOverlay`
|
|
3066
|
+
* without clobbering engine-authored `scopes`, `workItems`, or the
|
|
3067
|
+
* overlay-level `metadataPersistence` default. `setWorkflowOverlay`
|
|
3068
|
+
* replaces the overlay wholesale; passing `scopes: []` will drop
|
|
3069
|
+
* every scope registered via `addScope` and cause subsequent
|
|
3070
|
+
* `replaceText` calls to block with `workflow_comment_only`.
|
|
3035
3071
|
*/
|
|
3036
3072
|
getWorkflowOverlay(): WorkflowOverlay | null;
|
|
3037
3073
|
setSharedWorkflowState(state: SharedWorkflowState | null): void;
|
|
@@ -3272,7 +3308,31 @@ export interface WordReviewEditorProps {
|
|
|
3272
3308
|
suggestionsEnabled?: boolean;
|
|
3273
3309
|
chromePreset?: WordReviewEditorChromePreset;
|
|
3274
3310
|
chromeOptions?: Partial<WordReviewEditorChromeOptions>;
|
|
3275
|
-
|
|
3311
|
+
/**
|
|
3312
|
+
* Controls how tracked changes and comments render. Accepts Word's
|
|
3313
|
+
* 4-mode grammar (`"all-markup" | "simple-markup" | "no-markup" | "original"`)
|
|
3314
|
+
* or the legacy triple (`"all" | "simple" | "clean"`), which maps 1:1
|
|
3315
|
+
* onto the first three canonical names. `"original"` is new in 6d.N2:
|
|
3316
|
+
* insertions are hidden, deletions render as plain body text so the
|
|
3317
|
+
* reviewer sees the pre-change state.
|
|
3318
|
+
*/
|
|
3319
|
+
markupDisplay?:
|
|
3320
|
+
| "all-markup"
|
|
3321
|
+
| "simple-markup"
|
|
3322
|
+
| "no-markup"
|
|
3323
|
+
| "original"
|
|
3324
|
+
| "clean"
|
|
3325
|
+
| "simple"
|
|
3326
|
+
| "all";
|
|
3327
|
+
/**
|
|
3328
|
+
* L6d.N2 — invoked when the user picks a different display mode from
|
|
3329
|
+
* the in-chrome selector. Values are always emitted in the canonical
|
|
3330
|
+
* Word grammar (`"all-markup" | "simple-markup" | "no-markup" | "original"`),
|
|
3331
|
+
* not the legacy aliases.
|
|
3332
|
+
*/
|
|
3333
|
+
onMarkupDisplayChange?: (
|
|
3334
|
+
value: "all-markup" | "simple-markup" | "no-markup" | "original",
|
|
3335
|
+
) => void;
|
|
3276
3336
|
/**
|
|
3277
3337
|
* @internal HARNESS-ONLY debug-ports token.
|
|
3278
3338
|
*
|
|
@@ -138,6 +138,47 @@ export async function resolveChartPreviewsForDocument(
|
|
|
138
138
|
return applyResolutions(doc, successful);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
/**
|
|
142
|
+
* C3b — Phase A/B deferred chart preview resolution.
|
|
143
|
+
*
|
|
144
|
+
* Phase A (cheap, synchronous): check whether there are any unresolved
|
|
145
|
+
* chart_preview nodes. If not, return immediately without scheduling
|
|
146
|
+
* anything. The collect walk is O(paragraphs) and takes ~0 ms.
|
|
147
|
+
*
|
|
148
|
+
* Phase B (deferred): schedule the actual `renderChartPreview` calls
|
|
149
|
+
* outside the critical load path via requestIdleCallback (browser) or
|
|
150
|
+
* setTimeout(0) (Node / SSR). Fires `onReady` with the resolved
|
|
151
|
+
* CanonicalDocument when all charts complete.
|
|
152
|
+
*
|
|
153
|
+
* The caller (loadDocxEditorSessionAsync) returns the session without
|
|
154
|
+
* chart previews. When `onReady` fires, the consumer can update the
|
|
155
|
+
* runtime's media catalog (e.g. via a follow-up `hydrateChartPreviews`
|
|
156
|
+
* call, or by accepting the updated CanonicalDocument into a new
|
|
157
|
+
* progressive render). Expected win: 30–80 ms removed from the warm
|
|
158
|
+
* cold-open path on extra-large docs with ~12 charts.
|
|
159
|
+
*/
|
|
160
|
+
export function scheduleChartPreviewResolution(
|
|
161
|
+
doc: CanonicalDocument,
|
|
162
|
+
pkg: OpcPackage,
|
|
163
|
+
adapter: EditorHostAdapter | undefined,
|
|
164
|
+
onReady: (resolved: CanonicalDocument) => void,
|
|
165
|
+
): void {
|
|
166
|
+
if (!adapter?.renderChartPreview) return;
|
|
167
|
+
// Phase A: fast collect — avoids scheduling idle work when there are no
|
|
168
|
+
// unresolved charts (the common case for most non-chart documents).
|
|
169
|
+
const pending = collectUnresolvedChartPreviews(doc, pkg);
|
|
170
|
+
if (pending.length === 0) return;
|
|
171
|
+
|
|
172
|
+
const schedule =
|
|
173
|
+
typeof requestIdleCallback !== "undefined"
|
|
174
|
+
? (fn: () => void) => { requestIdleCallback(fn); }
|
|
175
|
+
: (fn: () => void) => { setTimeout(fn, 0); };
|
|
176
|
+
|
|
177
|
+
schedule(() => {
|
|
178
|
+
void resolveChartPreviewsForDocument(doc, pkg, adapter).then(onReady);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
141
182
|
/**
|
|
142
183
|
* Walk the whole content tree and yield one PendingResolution per
|
|
143
184
|
* chart_preview node that (a) has no previewMediaId yet and (b) can
|
package/src/io/docx-session.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
EditorError,
|
|
4
4
|
EditorHostAdapter,
|
|
5
5
|
EditorSessionState,
|
|
6
|
+
EditorSurfaceSnapshot,
|
|
6
7
|
EditorWarning as PublicEditorWarning,
|
|
7
8
|
EditorAnchorProjection as PublicEditorAnchorProjection,
|
|
8
9
|
ExportDocxOptions,
|
|
@@ -26,7 +27,13 @@ import type {
|
|
|
26
27
|
RevisionRecord as RuntimeRevisionRecord,
|
|
27
28
|
EditorWarning as InternalEditorWarning,
|
|
28
29
|
} from "../core/state/editor-state.ts";
|
|
29
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
createCanonicalDocumentId,
|
|
32
|
+
createDefaultCanonicalDocument,
|
|
33
|
+
createSelectionSnapshot,
|
|
34
|
+
} from "../core/state/editor-state.ts";
|
|
35
|
+
import { createEditorSurfaceSnapshot } from "../runtime/surface-projection.ts";
|
|
36
|
+
import { MAIN_STORY_TARGET } from "../core/selection/mapping.ts";
|
|
30
37
|
import {
|
|
31
38
|
createDetachedAnchor,
|
|
32
39
|
storyTargetsEqual,
|
|
@@ -44,7 +51,7 @@ import {
|
|
|
44
51
|
normalizeParsedTextDocument,
|
|
45
52
|
normalizeParsedTextDocumentAsync,
|
|
46
53
|
} from "./normalize/normalize-text.ts";
|
|
47
|
-
import { createChartPartLookup, resolveChartPreviewsForDocument } from "./chart-preview-resolver.ts";
|
|
54
|
+
import { createChartPartLookup, resolveChartPreviewsForDocument, scheduleChartPreviewResolution } from "./chart-preview-resolver.ts";
|
|
48
55
|
import { type LoadScheduler } from "./load-scheduler.ts";
|
|
49
56
|
import type { CacheEnvelope } from "../runtime/prerender/cache-envelope.ts";
|
|
50
57
|
import {
|
|
@@ -60,13 +67,12 @@ import {
|
|
|
60
67
|
getDocumentBackedWorkflowMetadata,
|
|
61
68
|
parseWorkflowPayloadEnvelopeFromPackage,
|
|
62
69
|
resolvePayloadPartPath,
|
|
70
|
+
resolveWorkflowPayloadPartPaths,
|
|
63
71
|
WORKFLOW_PAYLOAD_CONTENT_TYPE,
|
|
64
72
|
WORKFLOW_PAYLOAD_CUSTOM_PROPS_CONTENT_TYPE,
|
|
65
73
|
WORKFLOW_PAYLOAD_CUSTOM_PROPS_PART_PATH,
|
|
66
74
|
WORKFLOW_PAYLOAD_CUSTOM_PROPS_RELATIONSHIP_TYPE,
|
|
67
75
|
WORKFLOW_PAYLOAD_ITEM_PROPS_CONTENT_TYPE,
|
|
68
|
-
WORKFLOW_PAYLOAD_ITEM_PROPS_PART_PATH,
|
|
69
|
-
WORKFLOW_PAYLOAD_PART_PATH,
|
|
70
76
|
WORKFLOW_PAYLOAD_RELATIONSHIP_TYPE,
|
|
71
77
|
} from "./ooxml/workflow-payload.ts";
|
|
72
78
|
import {
|
|
@@ -122,6 +128,7 @@ import type {
|
|
|
122
128
|
SubPartsCatalog,
|
|
123
129
|
} from "../model/canonical-document.ts";
|
|
124
130
|
import { createCanonicalDocumentSignature } from "../model/canonical-document.ts";
|
|
131
|
+
import type { CanonicalDocument } from "../model/canonical-document.ts";
|
|
125
132
|
import type {
|
|
126
133
|
CommentImportDiagnostic,
|
|
127
134
|
ImportedCommentDefinition,
|
|
@@ -959,6 +966,53 @@ export interface LoadDocxEditorSessionAsyncOptions extends LoadDocxEditorSession
|
|
|
959
966
|
* path because their outputs are required by downstream consumers.
|
|
960
967
|
*/
|
|
961
968
|
laycacheEnvelope?: CacheEnvelope;
|
|
969
|
+
/**
|
|
970
|
+
* L7 Phase 2 Finale C2 — progressive initial mount. When supplied, the
|
|
971
|
+
* async loader fires this callback exactly once, after the body stage
|
|
972
|
+
* completes but before styles / sub-parts / compatibility / snapshot
|
|
973
|
+
* assembly, with a viewport-windowed `EditorSurfaceSnapshot`. The
|
|
974
|
+
* callback lets UI consumers show the first page's text before the
|
|
975
|
+
* rest of the load finishes, measurably shrinking perceived cold-open.
|
|
976
|
+
*
|
|
977
|
+
* The progressive surface is synthesized from a provisional
|
|
978
|
+
* `CanonicalDocumentEnvelope`: `content` is the normalized body; all
|
|
979
|
+
* other catalogs (styles, numbering, media, review, preservation) are
|
|
980
|
+
* empty. Viewport blocks render live; blocks beyond the viewport
|
|
981
|
+
* render as placeholders via the existing `cullBuild` flag. Consumers
|
|
982
|
+
* must treat the progressive surface as provisional — the final
|
|
983
|
+
* `LoadedDocxEditorSession` (returned from the same `await`) carries
|
|
984
|
+
* the real styled envelope.
|
|
985
|
+
*
|
|
986
|
+
* The callback receives `blocksRealized` (the viewport window size)
|
|
987
|
+
* and `blocksTotal` (total block count at body-stage time) so the
|
|
988
|
+
* consumer can size its viewport-commit telemetry.
|
|
989
|
+
*
|
|
990
|
+
* Optional. When absent, the async loader does not perform the
|
|
991
|
+
* provisional-envelope synthesis — no cold-path regression for
|
|
992
|
+
* consumers that do not opt in.
|
|
993
|
+
*
|
|
994
|
+
* Omitted on the Plan B short-circuit path (laycacheEnvelope !== undefined):
|
|
995
|
+
* the short-circuit is already fast enough that a progressive pre-commit
|
|
996
|
+
* adds more overhead than it saves.
|
|
997
|
+
*/
|
|
998
|
+
onProgressiveSnapshot?: (partial: {
|
|
999
|
+
surface: EditorSurfaceSnapshot;
|
|
1000
|
+
phase: "viewport";
|
|
1001
|
+
blocksRealized: number;
|
|
1002
|
+
blocksTotal: number;
|
|
1003
|
+
}) => void;
|
|
1004
|
+
/**
|
|
1005
|
+
* C3b — deferred chart preview resolution. When supplied, chart preview
|
|
1006
|
+
* rendering (`renderChartPreview()` calls) is removed from the critical
|
|
1007
|
+
* load path: the session returns without chart previews, and this
|
|
1008
|
+
* callback fires later (via requestIdleCallback / setTimeout) with the
|
|
1009
|
+
* CanonicalDocument that has all chart previews resolved. Expected win:
|
|
1010
|
+
* 30–80 ms on extra-large warm-path docs with ~12 charts.
|
|
1011
|
+
*
|
|
1012
|
+
* Consumers that need chart previews on first render should not supply
|
|
1013
|
+
* this callback — the blocking path remains available by omitting it.
|
|
1014
|
+
*/
|
|
1015
|
+
onChartPreviewsReady?: (resolvedDoc: CanonicalDocument) => void;
|
|
962
1016
|
}
|
|
963
1017
|
|
|
964
1018
|
/**
|
|
@@ -979,6 +1033,23 @@ export interface LoadDocxEditorSessionAsyncOptions extends LoadDocxEditorSession
|
|
|
979
1033
|
* and SSR. The DOM boundary in `editor-runtime-boundary.ts` calls this
|
|
980
1034
|
* async path so the browser can paint the skeleton mid-parse.
|
|
981
1035
|
*/
|
|
1036
|
+
|
|
1037
|
+
/**
|
|
1038
|
+
* L7 Phase 2 Finale C2 — progressive initial mount viewport window size.
|
|
1039
|
+
*
|
|
1040
|
+
* Sized to cover the first page of a typical paginated document (~20
|
|
1041
|
+
* body blocks = ~1 page on CCEP-scale templates at default margins). The
|
|
1042
|
+
* window is intentionally small so the provisional-envelope synthesis +
|
|
1043
|
+
* surface projection stays under ~30 ms — making `firstViewportCommit`
|
|
1044
|
+
* significantly faster than the full load.
|
|
1045
|
+
*
|
|
1046
|
+
* Blocks beyond this window render as `placeholder-culled` entries via
|
|
1047
|
+
* Phase 2.9's cullBuild flag (auto-derived from `!isInViewport`) — they
|
|
1048
|
+
* preserve `from`/`to` offsets for selection stability while costing ~0
|
|
1049
|
+
* style-cascade work.
|
|
1050
|
+
*/
|
|
1051
|
+
const PROGRESSIVE_VIEWPORT_BLOCKS = 20;
|
|
1052
|
+
|
|
982
1053
|
export async function loadDocxEditorSessionAsync(
|
|
983
1054
|
options: LoadDocxEditorSessionAsyncOptions,
|
|
984
1055
|
): Promise<LoadedDocxEditorSession> {
|
|
@@ -1090,18 +1161,45 @@ export async function loadDocxEditorSessionAsync(
|
|
|
1090
1161
|
const protectionSnapshot = buildProtectionSnapshot(documentProtection, []);
|
|
1091
1162
|
|
|
1092
1163
|
// Chart previews (`previewMediaId` is host-dependent) aren't cached
|
|
1093
|
-
// in the envelope
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1164
|
+
// in the envelope. C3b: when onChartPreviewsReady is provided, defer
|
|
1165
|
+
// resolution out of the critical path. Otherwise block (legacy behavior).
|
|
1166
|
+
let documentWithChartPreviews: CanonicalDocumentEnvelope;
|
|
1167
|
+
if (options.onChartPreviewsReady) {
|
|
1168
|
+
scheduleChartPreviewResolution(
|
|
1169
|
+
canonicalDocument,
|
|
1170
|
+
sourcePackage,
|
|
1171
|
+
options.hostAdapter,
|
|
1172
|
+
options.onChartPreviewsReady,
|
|
1173
|
+
);
|
|
1174
|
+
documentWithChartPreviews = canonicalDocument as CanonicalDocumentEnvelope;
|
|
1175
|
+
} else {
|
|
1176
|
+
documentWithChartPreviews = (await resolveChartPreviewsForDocument(
|
|
1177
|
+
canonicalDocument,
|
|
1178
|
+
sourcePackage,
|
|
1179
|
+
options.hostAdapter,
|
|
1180
|
+
)) as CanonicalDocumentEnvelope;
|
|
1181
|
+
}
|
|
1099
1182
|
|
|
1100
1183
|
const timestamp = new Date().toISOString();
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1184
|
+
// Phase 2 Finale C3: skip `buildCompatibilityReport` (60–100 ms on
|
|
1185
|
+
// extra-large) when the envelope carries a pre-computed report.
|
|
1186
|
+
// Pure-function determinism of the report is enforced by
|
|
1187
|
+
// `canonicalDocumentHash` (5th input to `deriveCacheKey`): any
|
|
1188
|
+
// change to the canonical doc flips the hash and rejects the
|
|
1189
|
+
// envelope on load.
|
|
1190
|
+
//
|
|
1191
|
+
// The cached report's `generatedAt` is a fixed sentinel
|
|
1192
|
+
// (`CACHE_NORMALIZED_GENERATED_AT`) for envelope byte-identity.
|
|
1193
|
+
// Swap it for the live ISO8601 timestamp here because downstream
|
|
1194
|
+
// `validatePersistedEditorSnapshot` requires
|
|
1195
|
+
// `$.compatibility.generatedAt` to be ISO 8601.
|
|
1196
|
+
const cachedReport = options.laycacheEnvelope?.compatibilityReport;
|
|
1197
|
+
const compatibility = cachedReport
|
|
1198
|
+
? { ...cachedReport, generatedAt: timestamp }
|
|
1199
|
+
: buildCompatibilityReport({
|
|
1200
|
+
document: documentWithChartPreviews,
|
|
1201
|
+
generatedAt: timestamp,
|
|
1202
|
+
});
|
|
1105
1203
|
await scheduler.yield();
|
|
1106
1204
|
|
|
1107
1205
|
const snapshot = createImportedSnapshot({
|
|
@@ -1225,6 +1323,64 @@ export async function loadDocxEditorSessionAsync(
|
|
|
1225
1323
|
);
|
|
1226
1324
|
stages.emit("body");
|
|
1227
1325
|
await scheduler.yield();
|
|
1326
|
+
|
|
1327
|
+
// L7 Phase 2 Finale C2 — progressive initial mount.
|
|
1328
|
+
//
|
|
1329
|
+
// Fire `onProgressiveSnapshot` exactly once, after the body stage and
|
|
1330
|
+
// its post-yield. At this point `normalizedDocument.content` carries
|
|
1331
|
+
// the full block tree with per-block runProperties already resolved
|
|
1332
|
+
// during `normalizeParsedTextDocumentAsync`. We synthesize a
|
|
1333
|
+
// throw-away `CanonicalDocumentEnvelope` using the normalized content
|
|
1334
|
+
// + empty style/review/preservation catalogs, then project a
|
|
1335
|
+
// viewport-windowed `EditorSurfaceSnapshot` (first `PROGRESSIVE_VIEWPORT_BLOCKS`
|
|
1336
|
+
// blocks real, rest as culled placeholders via the Phase 2.9 flag).
|
|
1337
|
+
//
|
|
1338
|
+
// The bench's measured signal: time from `loadDocxEditorSessionAsync`
|
|
1339
|
+
// entry to this callback's fire is `firstViewportCommitMs` — the
|
|
1340
|
+
// metric C2 gates on.
|
|
1341
|
+
//
|
|
1342
|
+
// Skipped on the Plan B short-circuit: `laycacheEnvelope !== undefined`
|
|
1343
|
+
// already completes ~376 ms faster than cold — adding a progressive
|
|
1344
|
+
// synthesis on top costs more than it saves. The short-circuit path
|
|
1345
|
+
// returns the real snapshot fast enough.
|
|
1346
|
+
if (
|
|
1347
|
+
options.onProgressiveSnapshot !== undefined &&
|
|
1348
|
+
options.laycacheEnvelope === undefined
|
|
1349
|
+
) {
|
|
1350
|
+
const provisionalDoc: CanonicalDocumentEnvelope = {
|
|
1351
|
+
...createDefaultCanonicalDocument(
|
|
1352
|
+
options.documentId,
|
|
1353
|
+
new Date().toISOString(),
|
|
1354
|
+
),
|
|
1355
|
+
content: normalizedDocument.content,
|
|
1356
|
+
};
|
|
1357
|
+
const blocksTotal = normalizedDocument.content.children.length;
|
|
1358
|
+
const blocksRealized = Math.min(
|
|
1359
|
+
PROGRESSIVE_VIEWPORT_BLOCKS,
|
|
1360
|
+
blocksTotal,
|
|
1361
|
+
);
|
|
1362
|
+
const progressiveSurface = createEditorSurfaceSnapshot(
|
|
1363
|
+
provisionalDoc,
|
|
1364
|
+
createSelectionSnapshot(0, 0),
|
|
1365
|
+
MAIN_STORY_TARGET,
|
|
1366
|
+
blocksRealized < blocksTotal
|
|
1367
|
+
? { viewportBlockRange: { start: 0, end: blocksRealized } }
|
|
1368
|
+
: undefined,
|
|
1369
|
+
);
|
|
1370
|
+
try {
|
|
1371
|
+
options.onProgressiveSnapshot({
|
|
1372
|
+
surface: progressiveSurface,
|
|
1373
|
+
phase: "viewport",
|
|
1374
|
+
blocksRealized,
|
|
1375
|
+
blocksTotal,
|
|
1376
|
+
});
|
|
1377
|
+
} catch {
|
|
1378
|
+
// A throwing consumer must not abort the load. Progressive is
|
|
1379
|
+
// a best-effort optimization; errors on the callback side
|
|
1380
|
+
// silently fall through to the normal full-commit path.
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1228
1384
|
const commentsPartPath = resolveCommentsPartPath(
|
|
1229
1385
|
sourcePackage,
|
|
1230
1386
|
mainDocumentPath,
|
|
@@ -1579,11 +1735,24 @@ export async function loadDocxEditorSessionAsync(
|
|
|
1579
1735
|
// chart_preview nodes inline so the first snapshot already carries the
|
|
1580
1736
|
// synthesized `previewMediaId`. Fallback-safe: returning null or throwing
|
|
1581
1737
|
// is per-chart — the typed badge renders as if the adapter weren't set.
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1738
|
+
// C3b: when onChartPreviewsReady is provided, defer resolution out of
|
|
1739
|
+
// the critical path (same pattern as the short-circuit branch above).
|
|
1740
|
+
let document: CanonicalDocumentEnvelope;
|
|
1741
|
+
if (options.onChartPreviewsReady) {
|
|
1742
|
+
scheduleChartPreviewResolution(
|
|
1743
|
+
importedDocument,
|
|
1744
|
+
sourcePackage,
|
|
1745
|
+
options.hostAdapter,
|
|
1746
|
+
options.onChartPreviewsReady,
|
|
1747
|
+
);
|
|
1748
|
+
document = importedDocument as CanonicalDocumentEnvelope;
|
|
1749
|
+
} else {
|
|
1750
|
+
document = (await resolveChartPreviewsForDocument(
|
|
1751
|
+
importedDocument,
|
|
1752
|
+
sourcePackage,
|
|
1753
|
+
options.hostAdapter,
|
|
1754
|
+
)) as CanonicalDocumentEnvelope;
|
|
1755
|
+
}
|
|
1587
1756
|
const compatibility = buildCompatibilityReport({
|
|
1588
1757
|
document,
|
|
1589
1758
|
generatedAt: timestamp,
|
|
@@ -1935,13 +2104,20 @@ function exportDocxEditorSession(
|
|
|
1935
2104
|
const hasSettingsSurface =
|
|
1936
2105
|
Boolean(state.sourceSettingsPartPath) ||
|
|
1937
2106
|
exportedSubParts?.settings !== undefined;
|
|
2107
|
+
const resolvedWorkflowPayloadPartPaths = resolveWorkflowPayloadPartPaths(
|
|
2108
|
+
state.sourcePackage,
|
|
2109
|
+
sessionState.documentId,
|
|
2110
|
+
);
|
|
2111
|
+
const internalEditorState = (
|
|
2112
|
+
options as { _editorState?: import("./ooxml/workflow-payload.ts").EditorStatePayload } | undefined
|
|
2113
|
+
)?._editorState;
|
|
1938
2114
|
|
|
1939
2115
|
const exportSession = createExportSession(state.sourcePackage, [
|
|
1940
2116
|
state.sourceDocumentPartPath,
|
|
1941
2117
|
APP_PROPERTIES_PART_PATH,
|
|
1942
2118
|
CORE_PROPERTIES_PART_PATH,
|
|
1943
|
-
|
|
1944
|
-
|
|
2119
|
+
resolvedWorkflowPayloadPartPaths.payloadPartPath,
|
|
2120
|
+
resolvedWorkflowPayloadPartPaths.itemPropsPartPath,
|
|
1945
2121
|
WORKFLOW_PAYLOAD_CUSTOM_PROPS_PART_PATH,
|
|
1946
2122
|
numberingPartPath,
|
|
1947
2123
|
commentsPartPath,
|
|
@@ -2190,8 +2366,14 @@ function exportDocxEditorSession(
|
|
|
2190
2366
|
|
|
2191
2367
|
ensureHostMetadataParts(exportSession, state.sourcePackage, currentDocument);
|
|
2192
2368
|
// Schema 1.2: pass through editorState payload collected by the runtime channel.
|
|
2193
|
-
|
|
2194
|
-
|
|
2369
|
+
ensureWorkflowPayloadParts(
|
|
2370
|
+
exportSession,
|
|
2371
|
+
sessionState,
|
|
2372
|
+
currentDocument,
|
|
2373
|
+
state.sourcePackage,
|
|
2374
|
+
resolvedWorkflowPayloadPartPaths,
|
|
2375
|
+
internalEditorState,
|
|
2376
|
+
);
|
|
2195
2377
|
|
|
2196
2378
|
return {
|
|
2197
2379
|
bytes: exportSession.serialize(),
|
|
@@ -3917,6 +4099,10 @@ function ensureWorkflowPayloadParts(
|
|
|
3917
4099
|
sessionState: EditorSessionState,
|
|
3918
4100
|
document: CanonicalDocumentEnvelope,
|
|
3919
4101
|
sourcePackage: OpcPackage,
|
|
4102
|
+
resolvedPartPaths: {
|
|
4103
|
+
payloadPartPath: string;
|
|
4104
|
+
itemPropsPartPath: string;
|
|
4105
|
+
},
|
|
3920
4106
|
editorState?: import("./ooxml/workflow-payload.ts").EditorStatePayload,
|
|
3921
4107
|
): void {
|
|
3922
4108
|
const payloadParts = buildWorkflowPayloadParts({
|
|
@@ -3932,6 +4118,14 @@ function ensureWorkflowPayloadParts(
|
|
|
3932
4118
|
if (!payloadParts) {
|
|
3933
4119
|
return;
|
|
3934
4120
|
}
|
|
4121
|
+
if (
|
|
4122
|
+
payloadParts.payloadPartPath !== resolvedPartPaths.payloadPartPath ||
|
|
4123
|
+
payloadParts.itemPropsPartPath !== resolvedPartPaths.itemPropsPartPath
|
|
4124
|
+
) {
|
|
4125
|
+
throw new Error(
|
|
4126
|
+
"Workflow payload export resolved inconsistent customXml paths; export session ownership no longer matches payload serialization.",
|
|
4127
|
+
);
|
|
4128
|
+
}
|
|
3935
4129
|
|
|
3936
4130
|
const payloadPart = sourcePackage.parts.get(payloadParts.payloadPartPath);
|
|
3937
4131
|
const itemPropsPart = sourcePackage.parts.get(payloadParts.itemPropsPartPath);
|
|
@@ -141,6 +141,10 @@ export const BROADCAST_COMMAND_TYPES: ReadonlySet<EditorCommand["type"]> = new S
|
|
|
141
141
|
"section.set-header-footer-link",
|
|
142
142
|
"content.insert-page-break",
|
|
143
143
|
"content.insert-table",
|
|
144
|
+
// C1: Shift+Tab list/paragraph de-indent — produces a document mutation, must broadcast
|
|
145
|
+
"text.outdent-tab",
|
|
146
|
+
// C2: host insertFragment() API — routes through executeEditorCommand same as other mutations
|
|
147
|
+
"fragment.insert",
|
|
144
148
|
]);
|
|
145
149
|
|
|
146
150
|
/**
|