@beyondwork/docx-react-component 1.0.37 → 1.0.39
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 +41 -31
- package/src/api/public-types.ts +496 -1
- package/src/core/commands/section-layout-commands.ts +58 -0
- package/src/core/commands/table-grid.ts +431 -0
- package/src/core/commands/table-structure-commands.ts +845 -56
- package/src/core/commands/text-commands.ts +122 -2
- package/src/io/docx-session.ts +1 -0
- package/src/io/export/serialize-main-document.ts +2 -11
- package/src/io/export/serialize-numbering.ts +43 -10
- package/src/io/export/serialize-paragraph-formatting.ts +152 -0
- package/src/io/export/serialize-run-formatting.ts +90 -0
- package/src/io/export/serialize-styles.ts +212 -0
- package/src/io/export/serialize-tables.ts +74 -0
- package/src/io/export/table-properties-xml.ts +139 -4
- package/src/io/normalize/normalize-text.ts +15 -0
- package/src/io/ooxml/parse-fields.ts +10 -3
- package/src/io/ooxml/parse-footnotes.ts +60 -0
- package/src/io/ooxml/parse-headers-footers.ts +60 -0
- package/src/io/ooxml/parse-main-document.ts +137 -0
- package/src/io/ooxml/parse-numbering.ts +41 -1
- package/src/io/ooxml/parse-paragraph-formatting.ts +188 -0
- package/src/io/ooxml/parse-run-formatting.ts +129 -0
- package/src/io/ooxml/parse-styles.ts +31 -0
- package/src/io/ooxml/parse-tables.ts +249 -0
- package/src/io/ooxml/xml-attr-helpers.ts +60 -0
- package/src/io/ooxml/xml-element.ts +19 -0
- package/src/model/canonical-document.ts +117 -3
- package/src/runtime/collab/event-types.ts +165 -0
- package/src/runtime/collab/index.ts +22 -0
- package/src/runtime/collab/remote-cursor-awareness.ts +93 -0
- package/src/runtime/collab/runtime-collab-sync.ts +273 -0
- package/src/runtime/document-layout.ts +4 -2
- package/src/runtime/document-navigation.ts +1 -1
- package/src/runtime/document-runtime.ts +248 -18
- package/src/runtime/layout/default-page-format.ts +96 -0
- package/src/runtime/layout/index.ts +47 -0
- package/src/runtime/layout/inert-layout-facet.ts +16 -0
- package/src/runtime/layout/layout-engine-instance.ts +100 -23
- package/src/runtime/layout/layout-invalidation.ts +14 -5
- package/src/runtime/layout/margin-preset-catalog.ts +178 -0
- package/src/runtime/layout/page-format-catalog.ts +233 -0
- package/src/runtime/layout/page-graph.ts +55 -0
- package/src/runtime/layout/paginate-paragraph-lines.ts +128 -0
- package/src/runtime/layout/paginated-layout-engine.ts +484 -37
- package/src/runtime/layout/project-block-fragments.ts +225 -0
- package/src/runtime/layout/public-facet.ts +748 -16
- package/src/runtime/layout/resolve-page-fields.ts +70 -0
- package/src/runtime/layout/resolve-page-previews.ts +185 -0
- package/src/runtime/layout/resolved-formatting-state.ts +30 -26
- package/src/runtime/layout/table-render-plan.ts +249 -0
- package/src/runtime/numbering-prefix.ts +5 -0
- package/src/runtime/paragraph-style-resolver.ts +194 -0
- package/src/runtime/render/block-fragment-projection.ts +35 -0
- package/src/runtime/render/decoration-resolver.ts +189 -0
- package/src/runtime/render/index.ts +57 -0
- package/src/runtime/render/pending-op-delta-reader.ts +129 -0
- package/src/runtime/render/render-frame-types.ts +317 -0
- package/src/runtime/render/render-kernel.ts +759 -0
- package/src/runtime/resolved-numbering-geometry.ts +9 -1
- package/src/runtime/surface-projection.ts +129 -9
- package/src/runtime/table-schema.ts +11 -0
- package/src/runtime/view-state.ts +67 -0
- package/src/runtime/workflow-markup.ts +1 -5
- package/src/runtime/workflow-rail-segments.ts +280 -0
- package/src/ui/WordReviewEditor.tsx +368 -19
- package/src/ui/editor-command-bag.ts +4 -0
- package/src/ui/editor-runtime-boundary.ts +16 -0
- package/src/ui/editor-shell-view.tsx +10 -0
- package/src/ui/editor-surface-controller.tsx +9 -1
- package/src/ui/headless/chrome-registry.ts +310 -15
- package/src/ui/headless/scoped-chrome-policy.ts +49 -1
- package/src/ui/headless/selection-tool-types.ts +10 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +23 -2
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +2 -14
- package/src/ui-tailwind/chrome/role-action-sets.ts +80 -0
- package/src/ui-tailwind/chrome/tw-detach-handle.tsx +147 -0
- package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +160 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +68 -92
- package/src/ui-tailwind/chrome/tw-selection-tool-placement.ts +149 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +11 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +15 -4
- package/src/ui-tailwind/chrome/tw-table-border-picker.tsx +245 -0
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +356 -140
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +284 -0
- package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +94 -0
- package/src/ui-tailwind/chrome-overlay/index.ts +16 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +96 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +178 -0
- package/src/ui-tailwind/editor-surface/fast-text-edit-lane.ts +4 -0
- package/src/ui-tailwind/editor-surface/local-edit-session-state.ts +11 -0
- package/src/ui-tailwind/editor-surface/page-slice-util.ts +15 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +7 -1
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +40 -4
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +22 -12
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +389 -0
- package/src/ui-tailwind/editor-surface/pm-schema.ts +40 -2
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +144 -62
- package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +179 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +559 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +224 -75
- package/src/ui-tailwind/editor-surface/tw-table-bands.css +61 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +19 -0
- package/src/ui-tailwind/index.ts +29 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +2 -2
- package/src/ui-tailwind/review/tw-rail-card.tsx +150 -0
- package/src/ui-tailwind/review/tw-review-rail-footer.tsx +52 -0
- package/src/ui-tailwind/review/tw-review-rail.tsx +166 -11
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +108 -0
- package/src/ui-tailwind/theme/editor-theme.css +498 -163
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +680 -0
- package/src/ui-tailwind/toolbar/tw-scope-posture-menu.tsx +182 -0
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +162 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +104 -2
- package/src/ui-tailwind/tw-review-workspace.tsx +234 -21
- package/src/runtime/collab-review-sync.ts +0 -254
- package/src/ui-tailwind/editor-surface/pm-collab-plugins.ts +0 -40
|
@@ -42,9 +42,11 @@ import {
|
|
|
42
42
|
createCommandBridgePlugins,
|
|
43
43
|
type CommandBridgeCallbacks,
|
|
44
44
|
} from "./pm-command-bridge";
|
|
45
|
-
import { createCollabPlugins } from "./pm-collab-plugins";
|
|
46
|
-
import { prosemirrorToYXmlFragment } from "y-prosemirror";
|
|
47
45
|
import { buildDecorations } from "./pm-decorations";
|
|
46
|
+
import { buildPageBreakDecorations } from "./pm-page-break-decorations";
|
|
47
|
+
import { DecorationSet } from "prosemirror-view";
|
|
48
|
+
import type { WordReviewEditorLayoutFacet } from "../../runtime/layout";
|
|
49
|
+
import { buildPagePreviewMaps } from "../../runtime/layout/resolve-page-previews";
|
|
48
50
|
import { createContextualInteractionPlugin } from "./pm-contextual-ui";
|
|
49
51
|
import {
|
|
50
52
|
finishPerfProbe,
|
|
@@ -65,6 +67,7 @@ import {
|
|
|
65
67
|
performSearch,
|
|
66
68
|
searchPluginKey,
|
|
67
69
|
} from "./search-plugin";
|
|
70
|
+
import { createRemoteCursorPlugin } from "./remote-cursor-plugin.ts";
|
|
68
71
|
import {
|
|
69
72
|
createSurfaceDecorationKey,
|
|
70
73
|
createSurfaceDocumentBuildKey,
|
|
@@ -73,12 +76,90 @@ import { tableNodeViews } from "./tw-table-node-view";
|
|
|
73
76
|
import type { SelectionToolbarAnchor } from "../../ui/headless/selection-toolbar-model";
|
|
74
77
|
import type { MediaPreviewDescriptor } from "./pm-state-from-snapshot";
|
|
75
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Build page-break widget decorations from the layout facet's current page
|
|
81
|
+
* graph. Returns `[]` when the facet is unavailable, the active story is
|
|
82
|
+
* not the main body, or there is only one page.
|
|
83
|
+
*
|
|
84
|
+
* The widget DOM renders the full inter-page chrome (footer band of prev
|
|
85
|
+
* page, visible gap, header band of next page) INLINE in the PM flow.
|
|
86
|
+
* Because it lives inside the flow, no absolute-positioned overlay is
|
|
87
|
+
* required — PM paints the chrome at exactly the right Y coordinate
|
|
88
|
+
* between adjacent blocks.
|
|
89
|
+
*/
|
|
90
|
+
function buildPageBreakDecorationsFromProps(
|
|
91
|
+
facet: WordReviewEditorLayoutFacet | undefined,
|
|
92
|
+
isMainStory: boolean,
|
|
93
|
+
positionMap: PositionMap,
|
|
94
|
+
posture: "canvas" | "page",
|
|
95
|
+
canonicalDocument: import("../../core/state/editor-state.ts").CanonicalDocumentEnvelope,
|
|
96
|
+
dimensions: {
|
|
97
|
+
headerBandPx?: number;
|
|
98
|
+
footerBandPx?: number;
|
|
99
|
+
interGapPx?: number;
|
|
100
|
+
} = {},
|
|
101
|
+
): ReturnType<typeof buildPageBreakDecorations> {
|
|
102
|
+
if (!facet || !isMainStory) return [];
|
|
103
|
+
if (typeof facet.getRenderFrame !== "function") return [];
|
|
104
|
+
const frame = facet.getRenderFrame();
|
|
105
|
+
if (!frame || frame.pages.length < 2) return [];
|
|
106
|
+
const fakeGraph = {
|
|
107
|
+
revision: frame.revision,
|
|
108
|
+
contentPageCount: frame.pages.filter((p) => !p.page.isBlankFiller).length,
|
|
109
|
+
pages: frame.pages.map((p) => ({
|
|
110
|
+
pageId: p.page.pageId,
|
|
111
|
+
pageIndex: p.page.pageIndex,
|
|
112
|
+
startOffset: p.page.startOffset,
|
|
113
|
+
isBlankFiller: p.page.isBlankFiller,
|
|
114
|
+
stories: {
|
|
115
|
+
displayPageNumber: p.page.stories.displayPageNumber,
|
|
116
|
+
header: p.page.stories.header,
|
|
117
|
+
footer: p.page.stories.footer,
|
|
118
|
+
},
|
|
119
|
+
})),
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Build per-page header/footer preview maps so the bands show live
|
|
123
|
+
// content (with PAGE/NUMPAGES resolved) instead of generic labels.
|
|
124
|
+
// We pass a graph-shape compatible adapter — `buildPagePreviewMaps`
|
|
125
|
+
// reads only `pages[].pageId / stories / isBlankFiller`, which the
|
|
126
|
+
// render-frame projection above already provides.
|
|
127
|
+
const subParts = canonicalDocument.subParts;
|
|
128
|
+
const previews =
|
|
129
|
+
subParts && (subParts.headers?.length || subParts.footers?.length)
|
|
130
|
+
? buildPagePreviewMaps(fakeGraph as never, {
|
|
131
|
+
headers: subParts.headers,
|
|
132
|
+
footers: subParts.footers,
|
|
133
|
+
})
|
|
134
|
+
: undefined;
|
|
135
|
+
|
|
136
|
+
return buildPageBreakDecorations({
|
|
137
|
+
graph: fakeGraph as never,
|
|
138
|
+
posture,
|
|
139
|
+
headerBandPx: dimensions.headerBandPx,
|
|
140
|
+
footerBandPx: dimensions.footerBandPx,
|
|
141
|
+
interGapPx: dimensions.interGapPx,
|
|
142
|
+
runtimeToPmOffset: (offset) => positionMap.runtimeToPm(offset),
|
|
143
|
+
headerPreviewByPageId: previews?.headerPreviewByPageId,
|
|
144
|
+
footerPreviewByPageId: previews?.footerPreviewByPageId,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function extractDecorations(
|
|
149
|
+
set: DecorationSet,
|
|
150
|
+
_doc: unknown,
|
|
151
|
+
): ReturnType<typeof buildPageBreakDecorations> {
|
|
152
|
+
// PM's DecorationSet doesn't expose its decorations directly; `find` with
|
|
153
|
+
// no range returns every decoration. This is the standard pattern when we
|
|
154
|
+
// need to merge sets.
|
|
155
|
+
return set.find() as unknown as ReturnType<typeof buildPageBreakDecorations>;
|
|
156
|
+
}
|
|
157
|
+
|
|
76
158
|
/**
|
|
77
159
|
* Same props interface as the legacy TwEditorSurface — drop-in replacement.
|
|
78
160
|
*/
|
|
79
161
|
export interface TwProseMirrorSurfaceProps {
|
|
80
162
|
currentUser: EditorUser;
|
|
81
|
-
ydoc?: import("yjs").Doc;
|
|
82
163
|
awareness?: import("y-protocols/awareness").Awareness;
|
|
83
164
|
snapshot: RuntimeRenderSnapshot;
|
|
84
165
|
canonicalDocument: CanonicalDocumentEnvelope;
|
|
@@ -100,6 +181,8 @@ export interface TwProseMirrorSurfaceProps {
|
|
|
100
181
|
onDeleteForward?: () => void;
|
|
101
182
|
onInsertTab?: () => void;
|
|
102
183
|
onOutdentTab?: () => void;
|
|
184
|
+
onListIndent?: () => void;
|
|
185
|
+
onListOutdent?: () => void;
|
|
103
186
|
onInsertHardBreak?: () => void;
|
|
104
187
|
onSplitParagraph?: () => void;
|
|
105
188
|
onUndo?: () => void;
|
|
@@ -125,6 +208,31 @@ export interface TwProseMirrorSurfaceProps {
|
|
|
125
208
|
dispatchRuntimeCommand?: (
|
|
126
209
|
command: import("./fast-text-edit-lane").LaneRuntimeCommand,
|
|
127
210
|
) => import("../../api/public-types").TextCommandAck;
|
|
211
|
+
/**
|
|
212
|
+
* Optional layout facet. When supplied and the active story is `main`,
|
|
213
|
+
* the surface injects widget decorations at every page boundary. The
|
|
214
|
+
* widget DOM renders full inter-page chrome (page-N footer band +
|
|
215
|
+
* visible gap + page-N+1 header band) INLINE, so chrome stays aligned
|
|
216
|
+
* with PM content without any absolute overlay.
|
|
217
|
+
*/
|
|
218
|
+
layoutFacet?: WordReviewEditorLayoutFacet;
|
|
219
|
+
/** Height in px of each page's header band. Default 32. */
|
|
220
|
+
pageChromeHeaderBandPx?: number;
|
|
221
|
+
/** Height in px of each page's footer band. Default 32. */
|
|
222
|
+
pageChromeFooterBandPx?: number;
|
|
223
|
+
/** Visible vertical gap between adjacent pages. Default 24 for page mode. */
|
|
224
|
+
pageChromeInterGapPx?: number;
|
|
225
|
+
/**
|
|
226
|
+
* Revision counter; incremented by the host to trigger a decoration
|
|
227
|
+
* rebuild when the render frame changes (zoom, incremental relayout).
|
|
228
|
+
*/
|
|
229
|
+
pageBreakRevision?: number;
|
|
230
|
+
/**
|
|
231
|
+
* Called when the user double-clicks a per-page header band.
|
|
232
|
+
* Receives the pageIndex of the band the user clicked.
|
|
233
|
+
*/
|
|
234
|
+
onOpenHeaderStoryForPage?: (pageIndex: number) => void;
|
|
235
|
+
onOpenFooterStoryForPage?: (pageIndex: number) => void;
|
|
128
236
|
}
|
|
129
237
|
|
|
130
238
|
export interface TwProseMirrorSurfaceRef {
|
|
@@ -162,6 +270,10 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
162
270
|
);
|
|
163
271
|
|
|
164
272
|
const mountRef = useRef<HTMLDivElement>(null);
|
|
273
|
+
const openHeaderForPageRef = useRef(props.onOpenHeaderStoryForPage);
|
|
274
|
+
const openFooterForPageRef = useRef(props.onOpenFooterStoryForPage);
|
|
275
|
+
openHeaderForPageRef.current = props.onOpenHeaderStoryForPage;
|
|
276
|
+
openFooterForPageRef.current = props.onOpenFooterStoryForPage;
|
|
165
277
|
const viewRef = useRef<EditorView | null>(null);
|
|
166
278
|
const positionMapRef = useRef<PositionMap | null>(null);
|
|
167
279
|
const callbacksRef = useRef<CommandBridgeCallbacks | null>(null);
|
|
@@ -202,6 +314,8 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
202
314
|
onInsertHardBreak: () => props.onInsertHardBreak?.(),
|
|
203
315
|
onInsertTab: () => props.onInsertTab?.(),
|
|
204
316
|
onOutdentTab: () => props.onOutdentTab?.(),
|
|
317
|
+
onListIndent: () => props.onListIndent?.(),
|
|
318
|
+
onListOutdent: () => props.onListOutdent?.(),
|
|
205
319
|
onUndo: () => props.onUndo?.(),
|
|
206
320
|
onRedo: () => props.onRedo?.(),
|
|
207
321
|
onBlockedInput: (command, message) => {
|
|
@@ -276,8 +390,6 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
276
390
|
],
|
|
277
391
|
);
|
|
278
392
|
|
|
279
|
-
const isCollabMode = Boolean(props.ydoc);
|
|
280
|
-
|
|
281
393
|
// Create PM plugins (stable across renders — callbacks accessed via ref)
|
|
282
394
|
const plugins = useMemo(() => {
|
|
283
395
|
const selectionCallbacks = {
|
|
@@ -291,70 +403,79 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
291
403
|
isPredicted: (opId) => sessionRef.current?.isPredicted(opId) ?? false,
|
|
292
404
|
});
|
|
293
405
|
|
|
294
|
-
const corePlugins =
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
});
|
|
406
|
+
const corePlugins = createCommandBridgePlugins({
|
|
407
|
+
...selectionCallbacks,
|
|
408
|
+
gate,
|
|
409
|
+
onInsertText: (text) => {
|
|
410
|
+
if (laneRef.current) {
|
|
411
|
+
laneRef.current.onInsertText(text);
|
|
412
|
+
} else {
|
|
413
|
+
callbacksRef.current?.onInsertText(text);
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
onDeleteBackward: () => {
|
|
417
|
+
if (laneRef.current) {
|
|
418
|
+
laneRef.current.onDeleteBackward();
|
|
419
|
+
} else {
|
|
420
|
+
callbacksRef.current?.onDeleteBackward();
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
onDeleteForward: () => {
|
|
424
|
+
if (laneRef.current) {
|
|
425
|
+
laneRef.current.onDeleteForward();
|
|
426
|
+
} else {
|
|
427
|
+
callbacksRef.current?.onDeleteForward();
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
onSplitParagraph: () => {
|
|
431
|
+
if (laneRef.current) {
|
|
432
|
+
laneRef.current.onSplitParagraph();
|
|
433
|
+
} else {
|
|
434
|
+
callbacksRef.current?.onSplitParagraph();
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
onInsertHardBreak: () => {
|
|
438
|
+
if (laneRef.current) {
|
|
439
|
+
laneRef.current.onInsertHardBreak();
|
|
440
|
+
} else {
|
|
441
|
+
callbacksRef.current?.onInsertHardBreak();
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
onInsertTab: () => callbacksRef.current?.onInsertTab(),
|
|
445
|
+
onOutdentTab: () => callbacksRef.current?.onOutdentTab?.(),
|
|
446
|
+
onListIndent: () => callbacksRef.current?.onListIndent?.(),
|
|
447
|
+
onListOutdent: () => callbacksRef.current?.onListOutdent?.(),
|
|
448
|
+
onUndo: () => callbacksRef.current?.onUndo(),
|
|
449
|
+
onRedo: () => callbacksRef.current?.onRedo(),
|
|
450
|
+
onBlockedInput: (command, message) => callbacksRef.current?.onBlockedInput?.(command, message),
|
|
451
|
+
onCompositionChange: (composing) => {
|
|
452
|
+
sessionRef.current?.setComposing(composing);
|
|
453
|
+
},
|
|
454
|
+
});
|
|
344
455
|
|
|
345
456
|
return [
|
|
346
457
|
...corePlugins,
|
|
458
|
+
...(props.awareness
|
|
459
|
+
? [
|
|
460
|
+
createRemoteCursorPlugin({
|
|
461
|
+
awareness: props.awareness,
|
|
462
|
+
localClientId: props.awareness.clientID,
|
|
463
|
+
getPositionMap: () => positionMapRef.current,
|
|
464
|
+
getActiveStory: () => snapshotRef.current.activeStory,
|
|
465
|
+
}),
|
|
466
|
+
]
|
|
467
|
+
: []),
|
|
347
468
|
createContextualInteractionPlugin({
|
|
348
469
|
onCommentActivated: (commentId) => props.onCommentActivated?.(commentId),
|
|
349
470
|
onRevisionActivated: (revisionId) => props.onRevisionActivated?.(revisionId),
|
|
350
471
|
}),
|
|
351
472
|
createSearchPlugin(),
|
|
352
473
|
];
|
|
353
|
-
}, [props.
|
|
474
|
+
}, [props.awareness, props.onCommentActivated, props.onRevisionActivated]);
|
|
354
475
|
|
|
355
476
|
const applyDecorationProps = useCallback(
|
|
356
477
|
(view: EditorView, positionMap: PositionMap): void => {
|
|
357
|
-
const
|
|
478
|
+
const baseDecorations = buildDecorations(
|
|
358
479
|
view.state.doc,
|
|
359
480
|
positionMap,
|
|
360
481
|
commentModel,
|
|
@@ -371,6 +492,24 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
371
492
|
props.activeWorkflowScopeIds,
|
|
372
493
|
props.workflowMetadata,
|
|
373
494
|
);
|
|
495
|
+
const pageBreakDecos = buildPageBreakDecorationsFromProps(
|
|
496
|
+
props.layoutFacet,
|
|
497
|
+
snapshot.activeStory.kind === "main",
|
|
498
|
+
positionMap,
|
|
499
|
+
props.isPageWorkspace ? "page" : "canvas",
|
|
500
|
+
props.canonicalDocument,
|
|
501
|
+
{
|
|
502
|
+
headerBandPx: props.pageChromeHeaderBandPx,
|
|
503
|
+
footerBandPx: props.pageChromeFooterBandPx,
|
|
504
|
+
interGapPx: props.pageChromeInterGapPx,
|
|
505
|
+
},
|
|
506
|
+
);
|
|
507
|
+
const decorations = pageBreakDecos.length > 0
|
|
508
|
+
? DecorationSet.create(view.state.doc, [
|
|
509
|
+
...pageBreakDecos,
|
|
510
|
+
...extractDecorations(baseDecorations, view.state.doc),
|
|
511
|
+
])
|
|
512
|
+
: baseDecorations;
|
|
374
513
|
view.setProps({
|
|
375
514
|
editable: () => canEdit,
|
|
376
515
|
decorations: () => decorations,
|
|
@@ -411,6 +550,33 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
411
550
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
412
551
|
}, []);
|
|
413
552
|
|
|
553
|
+
// Listen for the custom events dispatched by the page-chrome widget
|
|
554
|
+
// decorations (`wre-open-header-story-for-page` /
|
|
555
|
+
// `wre-open-footer-story-for-page`). The widget DOM lives inside the
|
|
556
|
+
// mount element, so bubbling events reach us here.
|
|
557
|
+
useEffect(() => {
|
|
558
|
+
const mount = mountRef.current;
|
|
559
|
+
if (!mount) return;
|
|
560
|
+
const handleHeader = (event: Event) => {
|
|
561
|
+
const detail = (event as CustomEvent<{ pageIndex: number }>).detail;
|
|
562
|
+
if (typeof detail?.pageIndex === "number") {
|
|
563
|
+
openHeaderForPageRef.current?.(detail.pageIndex);
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
const handleFooter = (event: Event) => {
|
|
567
|
+
const detail = (event as CustomEvent<{ pageIndex: number }>).detail;
|
|
568
|
+
if (typeof detail?.pageIndex === "number") {
|
|
569
|
+
openFooterForPageRef.current?.(detail.pageIndex);
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
mount.addEventListener("wre-open-header-story-for-page", handleHeader);
|
|
573
|
+
mount.addEventListener("wre-open-footer-story-for-page", handleFooter);
|
|
574
|
+
return () => {
|
|
575
|
+
mount.removeEventListener("wre-open-header-story-for-page", handleHeader);
|
|
576
|
+
mount.removeEventListener("wre-open-footer-story-for-page", handleFooter);
|
|
577
|
+
};
|
|
578
|
+
}, []);
|
|
579
|
+
|
|
414
580
|
// Build the FastTextEditLane whenever `dispatchRuntimeCommand` changes.
|
|
415
581
|
// The lane is consulted via `laneRef.current` inside PM plugin callbacks,
|
|
416
582
|
// so the plugins memo does not need to depend on this effect.
|
|
@@ -466,11 +632,6 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
466
632
|
useEffect(() => {
|
|
467
633
|
if (!mountRef.current || !surface) return;
|
|
468
634
|
|
|
469
|
-
// Collab mode: y-prosemirror owns the doc after initial mount
|
|
470
|
-
if (isCollabMode && viewRef.current) {
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
635
|
if (viewRef.current && documentBuildKeyRef.current === documentBuildKey) {
|
|
475
636
|
return;
|
|
476
637
|
}
|
|
@@ -540,15 +701,6 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
540
701
|
});
|
|
541
702
|
viewRef.current = view;
|
|
542
703
|
recordPerfSample("pm.mount");
|
|
543
|
-
|
|
544
|
-
if (isCollabMode && props.ydoc) {
|
|
545
|
-
const yXmlFragment = props.ydoc.getXmlFragment("prosemirror");
|
|
546
|
-
if (yXmlFragment.length === 0) {
|
|
547
|
-
props.ydoc.transact(() => {
|
|
548
|
-
prosemirrorToYXmlFragment(view.state.doc, yXmlFragment);
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
704
|
} else {
|
|
553
705
|
suppressSelectionEchoRef.current = true;
|
|
554
706
|
viewRef.current.updateState(state);
|
|
@@ -572,12 +724,10 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
572
724
|
}, [
|
|
573
725
|
applyDecorationProps,
|
|
574
726
|
documentBuildKey,
|
|
575
|
-
isCollabMode,
|
|
576
727
|
surface,
|
|
577
728
|
snapshot.selection,
|
|
578
729
|
plugins,
|
|
579
730
|
props.mediaPreviews,
|
|
580
|
-
props.ydoc,
|
|
581
731
|
]);
|
|
582
732
|
|
|
583
733
|
// Update decorations and editability without rebuilding the PM document.
|
|
@@ -610,7 +760,6 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
610
760
|
]);
|
|
611
761
|
|
|
612
762
|
useEffect(() => {
|
|
613
|
-
if (isCollabMode) return;
|
|
614
763
|
const view = viewRef.current;
|
|
615
764
|
const positionMap = positionMapRef.current;
|
|
616
765
|
if (!view || !surface || !positionMap) {
|
|
@@ -646,7 +795,7 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
646
795
|
queueMicrotask(() => {
|
|
647
796
|
suppressSelectionEchoRef.current = false;
|
|
648
797
|
});
|
|
649
|
-
}, [
|
|
798
|
+
}, [snapshot.selection, surface]);
|
|
650
799
|
|
|
651
800
|
useEffect(() => {
|
|
652
801
|
if (!pendingSelectionProbeRef.current) {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* R2c: table-style conditional-region band classes.
|
|
3
|
+
*
|
|
4
|
+
* These classes are applied to <td> elements by TwTableNodeView.applyCellAttrs
|
|
5
|
+
* whenever the surface snapshot's SurfaceTableCellSnapshot.bandClasses is set.
|
|
6
|
+
* The class set comes from `resolveTableStyleResolution().cells[*].activeConditionalRegions`
|
|
7
|
+
* mapped through the `band-<region>` convention.
|
|
8
|
+
*
|
|
9
|
+
* Band styling uses @apply against theme variables so light↔dark toggle repaints
|
|
10
|
+
* cell colors via the cascade instead of forcing a surface rebuild.
|
|
11
|
+
*
|
|
12
|
+
* Consumer integration: import this stylesheet alongside editor-theme.css in
|
|
13
|
+
* the app's build pipeline, e.g.:
|
|
14
|
+
* @import "@beyondwork/docx-react-component/ui-tailwind/editor-surface/tw-table-bands.css";
|
|
15
|
+
*
|
|
16
|
+
* Direct overrides: when a cell has a direct `shading.fill`, applyCellAttrs
|
|
17
|
+
* writes an inline `background-color` that wins over the band default.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
.band-firstRow {
|
|
21
|
+
@apply font-semibold bg-surface-raised;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.band-lastRow {
|
|
25
|
+
@apply font-semibold bg-surface-raised;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.band-firstColumn {
|
|
29
|
+
@apply font-semibold;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.band-lastColumn {
|
|
33
|
+
@apply font-semibold;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.band-band1Horz {
|
|
37
|
+
@apply bg-surface-secondary;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.band-band2Horz {
|
|
41
|
+
@apply bg-surface-tertiary;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.band-band1Vert {
|
|
45
|
+
@apply bg-surface-secondary/50;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.band-band2Vert {
|
|
49
|
+
@apply bg-surface-tertiary/50;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Corner cells — reserved for table styles that define them explicitly. */
|
|
53
|
+
.band-nwCell { /* top-left */ }
|
|
54
|
+
.band-neCell { /* top-right */ }
|
|
55
|
+
.band-swCell { /* bottom-left */ }
|
|
56
|
+
.band-seCell { /* bottom-right */ }
|
|
57
|
+
|
|
58
|
+
/* R3e: virtual header repeated on continuation pages. Visual parity with firstRow. */
|
|
59
|
+
.band-virtual-header {
|
|
60
|
+
@apply font-semibold bg-surface-raised;
|
|
61
|
+
}
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
import type { Node as PMNode } from "prosemirror-model";
|
|
12
12
|
import type { NodeViewConstructor, ViewMutationRecord } from "prosemirror-view";
|
|
13
13
|
|
|
14
|
+
// R2c: band class styles live in ./tw-table-bands.module.css. Consumers import
|
|
15
|
+
// that stylesheet through their build pipeline (same pattern as editor-theme.css).
|
|
16
|
+
// Importing .css directly from a .tsx breaks the node:test runner and the npm
|
|
17
|
+
// library build — keep CSS as a consumer-side concern.
|
|
18
|
+
|
|
14
19
|
const TABLE_LAYOUT_SYNC_EVENT = "pm-table-layout-sync";
|
|
15
20
|
|
|
16
21
|
interface TableCellLayout {
|
|
@@ -318,6 +323,7 @@ function applyCellAttrs(cell: HTMLTableCellElement, node: PMNode, isHeader: bool
|
|
|
318
323
|
const borderRight = node.attrs.borderRight as string | null | undefined;
|
|
319
324
|
const borderBottom = node.attrs.borderBottom as string | null | undefined;
|
|
320
325
|
const borderLeft = node.attrs.borderLeft as string | null | undefined;
|
|
326
|
+
const bandClasses = node.attrs.bandClasses as string | null | undefined;
|
|
321
327
|
|
|
322
328
|
if (backgroundColor) cell.setAttribute("data-cell-background", backgroundColor);
|
|
323
329
|
else cell.removeAttribute("data-cell-background");
|
|
@@ -332,6 +338,19 @@ function applyCellAttrs(cell: HTMLTableCellElement, node: PMNode, isHeader: bool
|
|
|
332
338
|
if (borderLeft) cell.setAttribute("data-border-left", borderLeft);
|
|
333
339
|
else cell.removeAttribute("data-border-left");
|
|
334
340
|
|
|
341
|
+
// R2b: band classes come from the resolved style cascade. They layer on top
|
|
342
|
+
// of the base Tailwind classes so theme vars (bg-surface-*, text-secondary,
|
|
343
|
+
// etc.) repaint on light↔dark toggle without a surface rebuild. Direct
|
|
344
|
+
// `backgroundColor` overrides still win via the inline-style block below.
|
|
345
|
+
if (bandClasses) {
|
|
346
|
+
cell.setAttribute("data-band-classes", bandClasses);
|
|
347
|
+
for (const token of bandClasses.split(/\s+/).filter(Boolean)) {
|
|
348
|
+
cell.classList.add(token);
|
|
349
|
+
}
|
|
350
|
+
} else {
|
|
351
|
+
cell.removeAttribute("data-band-classes");
|
|
352
|
+
}
|
|
353
|
+
|
|
335
354
|
cell.style.backgroundColor = backgroundColor ?? "";
|
|
336
355
|
cell.style.verticalAlign = verticalAlign === "center" ? "middle" : (verticalAlign ?? "");
|
|
337
356
|
cell.style.borderTop = borderTop ?? "";
|
package/src/ui-tailwind/index.ts
CHANGED
|
@@ -14,10 +14,31 @@ export { TwReviewRail, type TwReviewRailProps, type ReviewRailTab } from "./revi
|
|
|
14
14
|
export { TwCommentSidebar } from "./review/tw-comment-sidebar";
|
|
15
15
|
export { TwRevisionSidebar } from "./review/tw-revision-sidebar";
|
|
16
16
|
export { TwHealthPanel } from "./review/tw-health-panel";
|
|
17
|
+
export { TwWorkflowTab, type TwWorkflowTabProps } from "./review/tw-workflow-tab";
|
|
18
|
+
export {
|
|
19
|
+
TwRailCard,
|
|
20
|
+
type TwRailCardProps,
|
|
21
|
+
type RailCardTone,
|
|
22
|
+
type RailCardAvatar,
|
|
23
|
+
type RailCardCounter,
|
|
24
|
+
type RailCardProgress,
|
|
25
|
+
} from "./review/tw-rail-card";
|
|
26
|
+
export {
|
|
27
|
+
TwReviewRailFooter,
|
|
28
|
+
type TwReviewRailFooterProps,
|
|
29
|
+
} from "./review/tw-review-rail-footer";
|
|
17
30
|
|
|
18
31
|
// Toolbar
|
|
19
32
|
export { TwToolbar, type TwToolbarProps } from "./toolbar/tw-toolbar";
|
|
20
33
|
export { TwToolbarIconButton } from "./toolbar/tw-toolbar-icon-button";
|
|
34
|
+
export {
|
|
35
|
+
TwShellHeader,
|
|
36
|
+
type TwShellHeaderProps,
|
|
37
|
+
type ShellHeaderMode,
|
|
38
|
+
type ShellHeaderModeOption,
|
|
39
|
+
type ShellHeaderPrimaryAction,
|
|
40
|
+
type ShellHeaderIconAction,
|
|
41
|
+
} from "./toolbar/tw-shell-header";
|
|
21
42
|
export type { WorkspaceMode, ZoomLevel } from "../api/public-types";
|
|
22
43
|
|
|
23
44
|
// Status
|
|
@@ -27,6 +48,14 @@ export { TwStatusBar } from "./status/tw-status-bar";
|
|
|
27
48
|
export { TwAlertBanner } from "./chrome/tw-alert-banner";
|
|
28
49
|
export { TwSelectionToolbar } from "./chrome/tw-selection-toolbar";
|
|
29
50
|
|
|
51
|
+
// Chrome overlay plane (R3a — scope rail layer)
|
|
52
|
+
export {
|
|
53
|
+
TwChromeOverlay,
|
|
54
|
+
type TwChromeOverlayProps,
|
|
55
|
+
TwScopeRailLayer,
|
|
56
|
+
type TwScopeRailLayerProps,
|
|
57
|
+
} from "./chrome-overlay";
|
|
58
|
+
|
|
30
59
|
// Session capabilities
|
|
31
60
|
export {
|
|
32
61
|
deriveCapabilities,
|
|
@@ -93,10 +93,10 @@ function CommentThreadCard(props: {
|
|
|
93
93
|
role="button"
|
|
94
94
|
tabIndex={0}
|
|
95
95
|
className={[
|
|
96
|
-
"cursor-pointer rounded-
|
|
96
|
+
"cursor-pointer rounded-lg bg-surface/90 px-3 py-2.5 transition-colors ring-1 ring-border/40",
|
|
97
97
|
focusRingClass,
|
|
98
98
|
isActive
|
|
99
|
-
? "bg-accent-soft/40 ring-accent/25"
|
|
99
|
+
? "bg-accent-soft/40 ring-accent/25 shadow-[var(--shadow-soft)]"
|
|
100
100
|
: "hover:bg-surface",
|
|
101
101
|
thread.status === "detached" ? "opacity-70" : "",
|
|
102
102
|
].join(" ")}
|