@beyondwork/docx-react-component 1.0.47 → 1.0.49
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/README.md +16 -11
- package/package.json +30 -41
- package/src/api/public-types.ts +199 -13
- package/src/compare/diff-engine.ts +4 -0
- package/src/core/commands/add-scope.ts +257 -0
- package/src/core/commands/formatting-commands.ts +2 -0
- package/src/core/commands/index.ts +9 -1
- package/src/core/commands/text-commands.ts +3 -1
- package/src/core/schema/text-schema.ts +95 -1
- package/src/core/selection/anchor-conversion.ts +112 -0
- package/src/core/selection/review-anchors.ts +108 -3
- package/src/core/state/text-transaction.ts +103 -7
- package/src/internal/harness-debug-ports.ts +168 -0
- package/src/io/chart-preview-resolver.ts +59 -1
- package/src/io/docx-session.ts +226 -38
- package/src/io/export/serialize-main-document.ts +46 -0
- package/src/io/export/serialize-paragraph-formatting.ts +8 -0
- package/src/io/export/serialize-run-formatting.ts +10 -1
- package/src/io/export/serialize-settings.ts +421 -0
- package/src/io/export/serialize-styles.ts +10 -0
- package/src/io/normalize/normalize-text.ts +1 -0
- package/src/io/ooxml/chart/chart-style-table.ts +543 -0
- package/src/io/ooxml/chart/color-palette.ts +101 -0
- package/src/io/ooxml/chart/compose-series-color.ts +147 -0
- package/src/io/ooxml/chart/parse-axis.ts +277 -0
- package/src/io/ooxml/chart/parse-chart-space.ts +885 -0
- package/src/io/ooxml/chart/parse-series.ts +635 -0
- package/src/io/ooxml/chart/resolve-color.ts +261 -0
- package/src/io/ooxml/chart/types.ts +439 -0
- package/src/io/ooxml/parse-block-structure.ts +99 -0
- package/src/io/ooxml/parse-complex-content.ts +90 -2
- package/src/io/ooxml/parse-main-document.ts +156 -1
- package/src/io/ooxml/parse-paragraph-formatting.ts +46 -0
- package/src/io/ooxml/parse-run-formatting.ts +49 -0
- package/src/io/ooxml/parse-scope-markers.ts +184 -0
- package/src/io/ooxml/parse-settings-blueprint.ts +349 -0
- package/src/io/ooxml/parse-settings.ts +97 -1
- package/src/io/ooxml/parse-styles.ts +65 -0
- package/src/io/ooxml/parse-theme.ts +2 -127
- package/src/io/ooxml/property-grab-bag.ts +211 -0
- package/src/io/ooxml/xml-attr-helpers.ts +59 -1
- package/src/io/ooxml/xml-parser.ts +142 -0
- package/src/model/canonical-document.ts +160 -0
- package/src/model/scope-markers.ts +144 -0
- package/src/runtime/collab/base-doc-fingerprint.ts +99 -0
- package/src/runtime/collab/checkpoint-election.ts +75 -0
- package/src/runtime/collab/checkpoint-scheduler.ts +204 -0
- package/src/runtime/collab/checkpoint-store.ts +115 -0
- package/src/runtime/collab/event-types.ts +27 -0
- package/src/runtime/collab/index.ts +29 -0
- package/src/runtime/collab/remote-cursor-awareness.ts +167 -0
- package/src/runtime/collab/runtime-collab-sync.ts +330 -0
- package/src/runtime/collab/workflow-shared.ts +247 -0
- package/src/runtime/document-locations.ts +1 -9
- package/src/runtime/document-outline.ts +1 -9
- package/src/runtime/document-runtime.ts +288 -65
- package/src/runtime/editor-surface/capabilities.ts +63 -50
- package/src/runtime/hyperlink-color-resolver.ts +119 -0
- package/src/runtime/layout/layout-engine-version.ts +8 -1
- package/src/runtime/prerender/cache-envelope.ts +19 -7
- package/src/runtime/prerender/cache-key.ts +25 -14
- package/src/runtime/prerender/canonical-document-hash.ts +63 -0
- package/src/runtime/prerender/customxml-cache.ts +211 -0
- package/src/runtime/prerender/customxml-probe.ts +78 -0
- package/src/runtime/prerender/prerender-document.ts +74 -7
- package/src/runtime/scope-resolver.ts +148 -0
- package/src/runtime/scope-tag-registry.ts +10 -0
- package/src/runtime/surface-projection.ts +102 -37
- package/src/runtime/theme-color-resolver.ts +188 -0
- package/src/runtime/workflow-markup.ts +7 -18
- package/src/ui/WordReviewEditor.tsx +48 -2
- package/src/ui/editor-runtime-boundary.ts +42 -1
- package/src/ui/headless/selection-helpers.ts +10 -23
- package/src/ui/runtime-shortcut-dispatch.ts +12 -7
- package/src/ui/unsupported-previews-policy.ts +23 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +10 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +1 -0
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +47 -0
- package/src/ui-tailwind/page-stack/use-visible-block-range.ts +88 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +16 -1
|
@@ -128,6 +128,23 @@ export interface TwPageStackChromeLayerProps {
|
|
|
128
128
|
* DOM element; only the refocus step is skipped.
|
|
129
129
|
*/
|
|
130
130
|
pmView?: PmPortalView | null;
|
|
131
|
+
/**
|
|
132
|
+
* L7 Phase 2.8 — optional viewport cull. When supplied, the chrome
|
|
133
|
+
* layer only mounts per-page header / footer / footnote bands for
|
|
134
|
+
* pages whose sequential index falls inside this range (with overscan
|
|
135
|
+
* already applied by the caller). Pages outside the range render a
|
|
136
|
+
* lightweight wrapper `div` carrying the `data-page-chrome-frame` +
|
|
137
|
+
* `data-page-index` attributes — every position read (portal-slot
|
|
138
|
+
* query, per-page measurement) continues to resolve deterministically,
|
|
139
|
+
* but the four child React subtrees (Header/Footer/Footnote bands)
|
|
140
|
+
* never mount, eliminating the measured 412 ms Layout + 229 ms
|
|
141
|
+
* Pre-paint cost on 138-pp extra-large CCEP per the rev5 Chrome trace.
|
|
142
|
+
*
|
|
143
|
+
* Omit the prop (or pass `null`) to render every page — current
|
|
144
|
+
* behavior, used on first mount before the IntersectionObserver has
|
|
145
|
+
* reported.
|
|
146
|
+
*/
|
|
147
|
+
visiblePageIndexRange?: { start: number; end: number } | null;
|
|
131
148
|
/** Optional test id applied to the layer root. */
|
|
132
149
|
"data-testid"?: string;
|
|
133
150
|
}
|
|
@@ -140,6 +157,7 @@ export const TwPageStackChromeLayer: React.FC<TwPageStackChromeLayerProps> = ({
|
|
|
140
157
|
onOpenStory,
|
|
141
158
|
pmSurfaceElement,
|
|
142
159
|
pmView,
|
|
160
|
+
visiblePageIndexRange,
|
|
143
161
|
"data-testid": testId,
|
|
144
162
|
}) => {
|
|
145
163
|
const [rects, setRects] = React.useState<readonly PageOverlayRect[]>([]);
|
|
@@ -352,6 +370,35 @@ export const TwPageStackChromeLayer: React.FC<TwPageStackChromeLayerProps> = ({
|
|
|
352
370
|
const page = facet.getPage(pageIndex);
|
|
353
371
|
if (!page) return null;
|
|
354
372
|
|
|
373
|
+
// L7 Phase 2.8 — viewport cull. Pages outside the visible range
|
|
374
|
+
// (plus overscan) render an empty frame wrapper: the
|
|
375
|
+
// `data-page-chrome-frame` + `data-page-index` attributes stay
|
|
376
|
+
// put so downstream DOM queries (portal-slot, test hooks) keep
|
|
377
|
+
// working, but the four heavy React subtrees below do not mount.
|
|
378
|
+
const frameHeightPxForCull = rect.bottomPx - rect.topPx;
|
|
379
|
+
if (
|
|
380
|
+
visiblePageIndexRange &&
|
|
381
|
+
(pageIndex < visiblePageIndexRange.start ||
|
|
382
|
+
pageIndex >= visiblePageIndexRange.end)
|
|
383
|
+
) {
|
|
384
|
+
return (
|
|
385
|
+
<div
|
|
386
|
+
key={`page-chrome-${rect.pageId}`}
|
|
387
|
+
data-page-chrome-frame=""
|
|
388
|
+
data-page-index={pageIndex}
|
|
389
|
+
data-page-chrome-culled=""
|
|
390
|
+
style={{
|
|
391
|
+
position: "absolute",
|
|
392
|
+
top: `${rect.topPx}px`,
|
|
393
|
+
left: 0,
|
|
394
|
+
width: "100%",
|
|
395
|
+
height: `${frameHeightPxForCull}px`,
|
|
396
|
+
pointerEvents: "none",
|
|
397
|
+
}}
|
|
398
|
+
/>
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
355
402
|
const layout = page.layout;
|
|
356
403
|
const headerStory = page.stories.header;
|
|
357
404
|
const footerStory = page.stories.footer;
|
|
@@ -37,6 +37,19 @@ export interface BlockRange {
|
|
|
37
37
|
end: number; // exclusive
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* L7 Phase 2.8 — parallel result the chrome-overlay layers use to cull
|
|
42
|
+
* per-page React mounts. Expressed as sequential 0-based page indices
|
|
43
|
+
* (matching `facet.getPage(pageIndex)` + `pageMarkers.indexOf(marker)`),
|
|
44
|
+
* NOT the block indices stored on `data-page-frame`. `null` signals "no
|
|
45
|
+
* visibility signal yet — render every page" (initial-load fallback).
|
|
46
|
+
* Overscan is already applied to both sides.
|
|
47
|
+
*/
|
|
48
|
+
export interface VisiblePageIndexRange {
|
|
49
|
+
start: number; // inclusive
|
|
50
|
+
end: number; // exclusive
|
|
51
|
+
}
|
|
52
|
+
|
|
40
53
|
function readBlockIndex(el: HTMLElement, attr: string): number | null {
|
|
41
54
|
const v = el.getAttribute(attr);
|
|
42
55
|
if (v === null) return null;
|
|
@@ -155,3 +168,78 @@ export function useVisibleBlockRange(input: VisibleBlockRangeInput): BlockRange
|
|
|
155
168
|
};
|
|
156
169
|
}, [visiblePages, selectionBlockIndex, pageMarkers, overscanPages, totalBlockCount]);
|
|
157
170
|
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* L7 Phase 2.8 — sibling hook returning the visible sequential page index
|
|
174
|
+
* range for chrome-overlay viewport culling. Reuses the same
|
|
175
|
+
* IntersectionObserver wiring as `useVisibleBlockRange` but translates the
|
|
176
|
+
* visible-set of `data-page-frame` first-block-indices into SEQUENTIAL
|
|
177
|
+
* page indices via the order of `pageMarkers`. Returns `null` while the
|
|
178
|
+
* observer hasn't reported yet (fresh mount) — callers treat that as
|
|
179
|
+
* "render every page" and fall through to the current non-culled path.
|
|
180
|
+
*
|
|
181
|
+
* Overscan is applied on both sides so a single rapid scroll does not
|
|
182
|
+
* unmount chrome that is about to be revisible.
|
|
183
|
+
*/
|
|
184
|
+
export function useVisiblePageIndexRange(input: {
|
|
185
|
+
pageMarkers: readonly HTMLElement[];
|
|
186
|
+
overscanPages: number;
|
|
187
|
+
}): VisiblePageIndexRange | null {
|
|
188
|
+
const { pageMarkers, overscanPages } = input;
|
|
189
|
+
const [visiblePageFirstBlockIndices, setVisiblePageFirstBlockIndices] =
|
|
190
|
+
React.useState<Set<number>>(() => new Set());
|
|
191
|
+
|
|
192
|
+
React.useEffect(() => {
|
|
193
|
+
setVisiblePageFirstBlockIndices(new Set());
|
|
194
|
+
if (pageMarkers.length === 0) return;
|
|
195
|
+
const view = pageMarkers[0].ownerDocument?.defaultView;
|
|
196
|
+
if (!view?.IntersectionObserver) return;
|
|
197
|
+
|
|
198
|
+
const observer = new view.IntersectionObserver(
|
|
199
|
+
(entries) => {
|
|
200
|
+
setVisiblePageFirstBlockIndices((prev) => {
|
|
201
|
+
const next = new Set(prev);
|
|
202
|
+
let changed = false;
|
|
203
|
+
for (const entry of entries) {
|
|
204
|
+
const idx = readBlockIndex(entry.target as HTMLElement, "data-page-frame");
|
|
205
|
+
if (idx === null) continue;
|
|
206
|
+
const was = next.has(idx);
|
|
207
|
+
if (entry.isIntersecting && !was) {
|
|
208
|
+
next.add(idx);
|
|
209
|
+
changed = true;
|
|
210
|
+
} else if (!entry.isIntersecting && was) {
|
|
211
|
+
next.delete(idx);
|
|
212
|
+
changed = true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return changed ? next : prev;
|
|
216
|
+
});
|
|
217
|
+
},
|
|
218
|
+
{ root: null, rootMargin: "0px", threshold: 0 },
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
for (const marker of pageMarkers) observer.observe(marker);
|
|
222
|
+
return () => observer.disconnect();
|
|
223
|
+
}, [pageMarkers]);
|
|
224
|
+
|
|
225
|
+
return React.useMemo(() => {
|
|
226
|
+
if (pageMarkers.length === 0) return null;
|
|
227
|
+
if (visiblePageFirstBlockIndices.size === 0) return null;
|
|
228
|
+
|
|
229
|
+
let minSeq = Infinity;
|
|
230
|
+
let maxSeq = -Infinity;
|
|
231
|
+
pageMarkers.forEach((marker, seqIndex) => {
|
|
232
|
+
const firstBlockIdx = readBlockIndex(marker, "data-page-frame");
|
|
233
|
+
if (firstBlockIdx === null) return;
|
|
234
|
+
if (!visiblePageFirstBlockIndices.has(firstBlockIdx)) return;
|
|
235
|
+
if (seqIndex < minSeq) minSeq = seqIndex;
|
|
236
|
+
if (seqIndex > maxSeq) maxSeq = seqIndex;
|
|
237
|
+
});
|
|
238
|
+
if (minSeq === Infinity) return null;
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
start: Math.max(0, minSeq - overscanPages),
|
|
242
|
+
end: Math.min(pageMarkers.length, maxSeq + overscanPages + 1),
|
|
243
|
+
};
|
|
244
|
+
}, [visiblePageFirstBlockIndices, pageMarkers, overscanPages]);
|
|
245
|
+
}
|
|
@@ -52,7 +52,10 @@ import {
|
|
|
52
52
|
incrementInvalidationCounter,
|
|
53
53
|
recordPerfSample,
|
|
54
54
|
} from "./editor-surface/perf-probe.ts";
|
|
55
|
-
import {
|
|
55
|
+
import {
|
|
56
|
+
useVisibleBlockRange,
|
|
57
|
+
useVisiblePageIndexRange,
|
|
58
|
+
} from "./page-stack/use-visible-block-range.ts";
|
|
56
59
|
import { sliceBlocksForPage } from "./editor-surface/page-slice-util.ts";
|
|
57
60
|
import { TwPageBlockView } from "./editor-surface/tw-page-block-view.tsx";
|
|
58
61
|
import { computeLineMarkersIfEnabled } from "./page-chrome-model.ts";
|
|
@@ -982,6 +985,17 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
|
|
|
982
985
|
totalBlockCount: snapshot.surface?.blocks.length ?? 0,
|
|
983
986
|
});
|
|
984
987
|
|
|
988
|
+
// L7 Phase 2.8 — viewport cull for `TwPageStackChromeLayer`. Returns
|
|
989
|
+
// `null` while the IntersectionObserver hasn't reported yet; the chrome
|
|
990
|
+
// layer treats null as "render every page" so first paint is
|
|
991
|
+
// unaffected. Once the observer fires, the chrome layer only mounts
|
|
992
|
+
// bands for pages inside `[start, end)` plus overscan, eliminating the
|
|
993
|
+
// measured 412 ms Layout + 229 ms Pre-paint cost on 138-pp extra-large.
|
|
994
|
+
const visiblePageIndexRange = useVisiblePageIndexRange({
|
|
995
|
+
pageMarkers,
|
|
996
|
+
overscanPages: 2,
|
|
997
|
+
});
|
|
998
|
+
|
|
985
999
|
// Push the visible range into the layout facet (which delegates to the
|
|
986
1000
|
// runtime's viewport-culling machinery). Depend on `[start, end]` values
|
|
987
1001
|
// (not the range object) so identity-preserving updates are a no-op.
|
|
@@ -1649,6 +1663,7 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
|
|
|
1649
1663
|
activeStory={snapshot.activeStory}
|
|
1650
1664
|
onOpenStory={props.onOpenStory}
|
|
1651
1665
|
pmSurfaceElement={pmSurfaceElement}
|
|
1666
|
+
visiblePageIndexRange={visiblePageIndexRange}
|
|
1652
1667
|
/>
|
|
1653
1668
|
) : null}
|
|
1654
1669
|
</div>
|