@beyondwork/docx-react-component 1.0.74 → 1.0.76
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 +1 -1
- package/src/api/public-types.ts +7 -0
- package/src/api/v3/runtime/workflow.ts +130 -1
- package/src/io/ooxml/parse-headers-footers.ts +7 -13
- package/src/io/ooxml/parse-main-document.ts +7 -31
- package/src/io/ooxml/table-opaque-preservation.ts +171 -0
- package/src/runtime/document-runtime.ts +28 -1
- package/src/runtime/surface-projection.ts +9 -0
- package/src/runtime/workflow/scope-writer.ts +212 -10
- package/src/ui-tailwind/editor-surface/fast-text-edit-lane.ts +44 -5
- package/src/ui-tailwind/editor-surface/pm-schema.ts +22 -2
- package/src/ui-tailwind/editor-surface/preserve-position.ts +211 -0
- package/src/ui-tailwind/editor-surface/scroll-anchor.ts +8 -5
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +22 -3
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +49 -5
- package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +7 -3
- package/src/ui-tailwind/page-stack/tw-endnote-area.tsx +5 -1
- package/src/ui-tailwind/page-stack/tw-footnote-area.tsx +5 -1
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +7 -5
- package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +0 -13
- package/src/ui-tailwind/tw-review-workspace.tsx +0 -6
|
@@ -65,6 +65,7 @@ import { buildPositionMap, type PositionMap } from "./pm-position-map";
|
|
|
65
65
|
import { createLocalEditSessionState } from "./local-edit-session-state";
|
|
66
66
|
import { createFastTextEditLane } from "./fast-text-edit-lane";
|
|
67
67
|
import { createPredictedTxGate } from "./predicted-tx-gate";
|
|
68
|
+
import { replaceStatePreservingPosition } from "./preserve-position";
|
|
68
69
|
import {
|
|
69
70
|
createScopeTagRegistry,
|
|
70
71
|
type ScopeTagRegistry,
|
|
@@ -772,11 +773,35 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
772
773
|
laneRef.current = null;
|
|
773
774
|
return;
|
|
774
775
|
}
|
|
776
|
+
// Wave 1 Slice E1/E2 — lane observability.
|
|
777
|
+
//
|
|
778
|
+
// `typing.reconcile` measures the dispatch → ack window per keystroke
|
|
779
|
+
// (predicted path). `typing.divergence` fires on the
|
|
780
|
+
// structural-divergence ack kind (the rollback-all path). Both probe
|
|
781
|
+
// kinds are declared in `PerfProbeKind` and were previously
|
|
782
|
+
// unemitted — wiring them here closes the instrumentation gap so
|
|
783
|
+
// lane quality regressions show up in the standard perf summary.
|
|
784
|
+
const pendingReconcileTokens = new Map<string, string | null>();
|
|
775
785
|
laneRef.current = createFastTextEditLane({
|
|
776
786
|
session: sessionRef.current,
|
|
777
787
|
getView: () => viewRef.current,
|
|
778
788
|
getPositionMap: () => positionMapRef.current,
|
|
779
789
|
dispatchRuntimeCommand: props.dispatchRuntimeCommand,
|
|
790
|
+
probe: {
|
|
791
|
+
markPredicted(opId: string) {
|
|
792
|
+
pendingReconcileTokens.set(opId, startPerfProbe("typing.reconcile"));
|
|
793
|
+
},
|
|
794
|
+
markReconciled(opId: string, kind) {
|
|
795
|
+
const token = pendingReconcileTokens.get(opId);
|
|
796
|
+
if (token !== undefined) {
|
|
797
|
+
finishPerfProbe(token);
|
|
798
|
+
pendingReconcileTokens.delete(opId);
|
|
799
|
+
}
|
|
800
|
+
if (kind === "structural-divergence") {
|
|
801
|
+
recordPerfSample("typing.divergence");
|
|
802
|
+
}
|
|
803
|
+
},
|
|
804
|
+
},
|
|
780
805
|
suppressSelectionSync: (suppressed) => {
|
|
781
806
|
suppressSelectionEchoRef.current = suppressed;
|
|
782
807
|
},
|
|
@@ -891,11 +916,30 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
891
916
|
viewRef.current = view;
|
|
892
917
|
recordPerfSample("pm.mount");
|
|
893
918
|
} else {
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
919
|
+
// Wave 1 Slice C · the single funnel for snapshot replacement.
|
|
920
|
+
//
|
|
921
|
+
// `replaceStatePreservingPosition` encapsulates two invariants:
|
|
922
|
+
// 1. Scroll position preservation — capture the anchor block
|
|
923
|
+
// before `view.updateState`, restore scroll after, so the
|
|
924
|
+
// user's viewport doesn't jump when blocks above change
|
|
925
|
+
// height (invariant 7: geometry-facet warm path, no DOM
|
|
926
|
+
// measurement on the hot path).
|
|
927
|
+
// 2. Echo-suppression ordering — `suppressSelectionEchoRef` is
|
|
928
|
+
// set to `true` BEFORE the state swap and released in a
|
|
929
|
+
// microtask AFTER, so PM's internal selection-change events
|
|
930
|
+
// during the swap are swallowed by the selection-sync
|
|
931
|
+
// plugin.
|
|
932
|
+
//
|
|
933
|
+
// Ordering is regression-guarded by
|
|
934
|
+
// `test/ui-tailwind/editor-surface/preserve-position-ordering.test.ts`.
|
|
935
|
+
replaceStatePreservingPosition(
|
|
936
|
+
{
|
|
937
|
+
view: viewRef.current,
|
|
938
|
+
geometryFacet: props.geometryFacet,
|
|
939
|
+
suppressionRef: suppressSelectionEchoRef,
|
|
940
|
+
},
|
|
941
|
+
state,
|
|
942
|
+
);
|
|
899
943
|
}
|
|
900
944
|
documentBuildKeyRef.current = documentBuildKey;
|
|
901
945
|
applyDecorationProps(viewRef.current, positionMap);
|
|
@@ -145,11 +145,15 @@ export function collectFloatingImageOverlayItems(input: {
|
|
|
145
145
|
};
|
|
146
146
|
|
|
147
147
|
// coord-01 §9 / §5.1 — CCEP logos live in header stories; collect from
|
|
148
|
-
// the
|
|
149
|
-
//
|
|
150
|
-
// story
|
|
148
|
+
// the active story's surface.blocks (always) + every secondary story
|
|
149
|
+
// EXCEPT the one whose target matches activeStory (runtime fills
|
|
150
|
+
// surface.blocks with that same story's blocks, so walking secondary
|
|
151
|
+
// stories unconditionally would double-emit header/footer segments
|
|
152
|
+
// the moment the user enters a header for editing — M1).
|
|
151
153
|
collectFromStory(surface.blocks, activeStory);
|
|
154
|
+
const activeKey = storyTargetKey(activeStory);
|
|
152
155
|
for (const secondary of surface.secondaryStories ?? []) {
|
|
156
|
+
if (storyTargetKey(secondary.target) === activeKey) continue;
|
|
153
157
|
collectFromStory(secondary.blocks, secondary.target);
|
|
154
158
|
}
|
|
155
159
|
|
|
@@ -52,7 +52,11 @@ export const TwEndnoteArea: React.FC<TwEndnoteAreaProps> = ({
|
|
|
52
52
|
marginBottom: "8pt",
|
|
53
53
|
}}
|
|
54
54
|
/>
|
|
55
|
-
<TwRegionBlockRenderer
|
|
55
|
+
<TwRegionBlockRenderer
|
|
56
|
+
blocks={blocks}
|
|
57
|
+
mediaPreviews={mediaPreviews}
|
|
58
|
+
fallbackDisplay="hidden"
|
|
59
|
+
/>
|
|
56
60
|
</div>
|
|
57
61
|
);
|
|
58
62
|
};
|
|
@@ -66,7 +66,11 @@ export const TwFootnoteArea: React.FC<TwFootnoteAreaProps> = React.memo(({
|
|
|
66
66
|
marginBottom: "4pt",
|
|
67
67
|
}}
|
|
68
68
|
/>
|
|
69
|
-
<TwRegionBlockRenderer
|
|
69
|
+
<TwRegionBlockRenderer
|
|
70
|
+
blocks={blocks}
|
|
71
|
+
mediaPreviews={mediaPreviews}
|
|
72
|
+
fallbackDisplay="hidden"
|
|
73
|
+
/>
|
|
70
74
|
</div>
|
|
71
75
|
);
|
|
72
76
|
});
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
headingClassList,
|
|
15
15
|
resolveHeadingLevel,
|
|
16
16
|
} from "../editor-surface/tw-page-block-view.helpers.ts";
|
|
17
|
+
import { shouldRenderAbsoluteFloatingImageInPageOverlay } from "./floating-image-overlay-model.ts";
|
|
17
18
|
|
|
18
19
|
const EMU_PER_PX = 9525;
|
|
19
20
|
|
|
@@ -94,11 +95,12 @@ function renderSegment(
|
|
|
94
95
|
case "hard_break":
|
|
95
96
|
return <br key={seg.segmentId} />;
|
|
96
97
|
case "image": {
|
|
97
|
-
// §5.1 gap 3 — floating-anchor images
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
|
|
98
|
+
// §5.1 gap 3 — floating-anchor images the overlay can render
|
|
99
|
+
// (`TwFloatingImageLayer`) are owned by the overlay. Skip inline
|
|
100
|
+
// emission ONLY for anchors the overlay predicate accepts —
|
|
101
|
+
// otherwise wrap-mode=square / column-relative / tight-wrapped
|
|
102
|
+
// floats get dropped from inline AND from the overlay (M2).
|
|
103
|
+
if (shouldRenderAbsoluteFloatingImageInPageOverlay(seg.anchor)) {
|
|
102
104
|
return null;
|
|
103
105
|
}
|
|
104
106
|
// Mirror body-renderer behavior (`pm-state-from-snapshot.ts` :500+):
|
|
@@ -2,7 +2,6 @@ import { useCallback, useEffect } from "react";
|
|
|
2
2
|
import type { Dispatch, SetStateAction } from "react";
|
|
3
3
|
|
|
4
4
|
import { createCanvasBackend } from "../../api/public-types.ts";
|
|
5
|
-
import type { RuntimeRenderSnapshot } from "../../api/public-types.ts";
|
|
6
5
|
import {
|
|
7
6
|
incrementInvalidationCounter,
|
|
8
7
|
recordPerfSample,
|
|
@@ -17,8 +16,6 @@ export interface UseWorkspaceSideEffectsOptions {
|
|
|
17
16
|
activeParagraphLayout: ActiveParagraphLayout | null;
|
|
18
17
|
pageChromeModel: PageChromeModel;
|
|
19
18
|
pageShellMetrics: PageShellMetrics;
|
|
20
|
-
isPageWorkspace: boolean;
|
|
21
|
-
activeStoryKind: RuntimeRenderSnapshot["activeStory"]["kind"];
|
|
22
19
|
showDrawerReviewRail: boolean;
|
|
23
20
|
setReviewRailOpen: Dispatch<SetStateAction<boolean>>;
|
|
24
21
|
onOpenHeaderStory?: () => void;
|
|
@@ -58,8 +55,6 @@ export function useWorkspaceSideEffects(
|
|
|
58
55
|
activeParagraphLayout,
|
|
59
56
|
pageChromeModel,
|
|
60
57
|
pageShellMetrics,
|
|
61
|
-
isPageWorkspace,
|
|
62
|
-
activeStoryKind,
|
|
63
58
|
showDrawerReviewRail,
|
|
64
59
|
setReviewRailOpen,
|
|
65
60
|
onOpenHeaderStory,
|
|
@@ -67,14 +62,6 @@ export function useWorkspaceSideEffects(
|
|
|
67
62
|
onDismissSelectionToolbar,
|
|
68
63
|
} = options;
|
|
69
64
|
|
|
70
|
-
// Slice A (designsystem §6.20 reshape, 2026-04-24): isPageWorkspace +
|
|
71
|
-
// activeStoryKind referenced here so the prop sweep stays a no-op
|
|
72
|
-
// type-checker pass even though the auto-open-layout-tools effect
|
|
73
|
-
// they fed retired with the strip. Slice B mounts an active-band
|
|
74
|
-
// ribbon that observes activeStoryKind directly.
|
|
75
|
-
void isPageWorkspace;
|
|
76
|
-
void activeStoryKind;
|
|
77
|
-
|
|
78
65
|
useEffect(() => {
|
|
79
66
|
recordPerfSample("workspace.chrome");
|
|
80
67
|
incrementInvalidationCounter("workspace.chrome.recomputes");
|
|
@@ -10,10 +10,6 @@ import React, {
|
|
|
10
10
|
import * as Tooltip from "@radix-ui/react-tooltip";
|
|
11
11
|
import { ChevronRight } from "lucide-react";
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
useVisibleBlockRange,
|
|
15
|
-
useVisiblePageIndexRange,
|
|
16
|
-
} from "./page-stack/use-visible-block-range.ts";
|
|
17
13
|
import { sliceBlocksForPage } from "./editor-surface/page-slice-util.ts";
|
|
18
14
|
import {
|
|
19
15
|
findScrollAnchor,
|
|
@@ -330,8 +326,6 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
|
|
|
330
326
|
activeParagraphLayout,
|
|
331
327
|
pageChromeModel,
|
|
332
328
|
pageShellMetrics,
|
|
333
|
-
isPageWorkspace,
|
|
334
|
-
activeStoryKind: snapshot.activeStory.kind,
|
|
335
329
|
showDrawerReviewRail: responsiveChrome.showDrawerReviewRail,
|
|
336
330
|
setReviewRailOpen,
|
|
337
331
|
onOpenHeaderStory: props.onOpenHeaderStory,
|