@beyondwork/docx-react-component 1.0.58 → 1.0.59
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 +2 -2
- package/package.json +2 -1
- package/src/api/awareness-identity-types.ts +4 -2
- package/src/api/comment-negotiation-types.ts +4 -1
- package/src/api/external-custody-types.ts +16 -0
- package/src/api/internal/build-ref-projections.ts +108 -0
- package/src/api/package-version.ts +1 -1
- package/src/api/participants-types.ts +11 -1
- package/src/api/public-types.ts +978 -10
- package/src/api/scope-metadata-resolver-types.ts +6 -0
- package/src/compare/diff-engine.ts +3 -0
- package/src/core/commands/formatting-commands.ts +1 -0
- package/src/core/commands/index.ts +225 -16
- package/src/core/commands/legacy-form-field-commands.ts +181 -0
- package/src/core/commands/table-structure-commands.ts +149 -31
- package/src/core/selection/mapping.ts +20 -0
- package/src/core/state/editor-state.ts +2 -1
- package/src/index.ts +28 -0
- package/src/io/docx-session.ts +22 -3
- package/src/io/export/export-session.ts +11 -7
- package/src/io/export/ooxml-namespaces.ts +47 -0
- package/src/io/export/reattach-preserved-parts.ts +4 -16
- package/src/io/export/serialize-comments.ts +3 -131
- package/src/io/export/serialize-ffdata.ts +89 -0
- package/src/io/export/serialize-headers-footers.ts +5 -0
- package/src/io/export/serialize-main-document.ts +224 -34
- package/src/io/export/serialize-numbering.ts +22 -2
- package/src/io/export/serialize-revisions.ts +99 -0
- package/src/io/export/serialize-tables.ts +9 -0
- package/src/io/export/split-review-boundaries.ts +1 -0
- package/src/io/export/table-properties-xml.ts +14 -0
- package/src/io/load-scheduler.ts +70 -28
- package/src/io/normalize/normalize-text.ts +13 -0
- package/src/io/ooxml/_mini-xml.ts +198 -0
- package/src/io/ooxml/canonicalize-payload.ts +1 -4
- package/src/io/ooxml/chart/chart-style-table.ts +4 -3
- package/src/io/ooxml/chart/parse-chart-space.ts +2 -4
- package/src/io/ooxml/chart/parse-series.ts +2 -1
- package/src/io/ooxml/chart/resolve-color.ts +2 -2
- package/src/io/ooxml/chart/types.ts +6 -434
- package/src/io/ooxml/comment-presentation-payload.ts +6 -5
- package/src/io/ooxml/highlight-colors.ts +8 -5
- package/src/io/ooxml/parse-anchor.ts +68 -53
- package/src/io/ooxml/parse-comments.ts +14 -142
- package/src/io/ooxml/parse-complex-content.ts +3 -106
- package/src/io/ooxml/parse-drawing.ts +100 -195
- package/src/io/ooxml/parse-ffdata.ts +93 -0
- package/src/io/ooxml/parse-fields.ts +7 -146
- package/src/io/ooxml/parse-fill.ts +88 -8
- package/src/io/ooxml/parse-font-table.ts +5 -105
- package/src/io/ooxml/parse-footnotes.ts +28 -152
- package/src/io/ooxml/parse-headers-footers.ts +106 -212
- package/src/io/ooxml/parse-inline-media.ts +3 -200
- package/src/io/ooxml/parse-main-document.ts +180 -217
- package/src/io/ooxml/parse-numbering.ts +154 -335
- package/src/io/ooxml/parse-object.ts +147 -0
- package/src/io/ooxml/parse-ole-relationship.ts +82 -0
- package/src/io/ooxml/parse-paragraph-formatting.ts +7 -10
- package/src/io/ooxml/parse-picture-sdt.ts +85 -0
- package/src/io/ooxml/parse-picture.ts +72 -42
- package/src/io/ooxml/parse-revisions.ts +285 -51
- package/src/io/ooxml/parse-settings.ts +6 -99
- package/src/io/ooxml/parse-shapes.ts +25 -140
- package/src/io/ooxml/parse-styles.ts +3 -218
- package/src/io/ooxml/parse-tables.ts +76 -256
- package/src/io/ooxml/parse-theme.ts +1 -4
- package/src/io/ooxml/property-grab-bag.ts +5 -47
- package/src/io/ooxml/xml-element-serialize.ts +32 -0
- package/src/io/ooxml/xml-parser.ts +183 -0
- package/src/legal/bookmarks.ts +1 -1
- package/src/legal/cross-references.ts +1 -1
- package/src/legal/defined-terms.ts +1 -1
- package/src/legal/{_document-root.ts → document-root.ts} +8 -0
- package/src/legal/signature-blocks.ts +1 -1
- package/src/model/canonical-document.ts +159 -6
- package/src/model/chart-types.ts +439 -0
- package/src/model/snapshot.ts +3 -1
- package/src/review/store/comment-remapping.ts +24 -11
- package/src/review/store/revision-actions.ts +482 -2
- package/src/review/store/revision-store.ts +15 -0
- package/src/review/store/revision-types.ts +76 -0
- package/src/runtime/collab/remote-cursor-awareness.ts +24 -0
- package/src/runtime/collab/runtime-collab-sync.ts +33 -0
- package/src/runtime/diagnostics/build-diagnostic.ts +151 -0
- package/src/runtime/diagnostics/code-metadata-table.ts +221 -0
- package/src/runtime/document-runtime.ts +476 -34
- package/src/runtime/document-search.ts +115 -0
- package/src/runtime/edit-ops/index.ts +18 -2
- package/src/runtime/footnote-resolver.ts +130 -0
- package/src/runtime/layout/layout-engine-instance.ts +31 -4
- package/src/runtime/layout/layout-engine-version.ts +37 -1
- package/src/runtime/layout/page-graph.ts +14 -1
- package/src/runtime/layout/resolved-formatting-state.ts +21 -0
- package/src/runtime/numbering-prefix.ts +17 -0
- package/src/runtime/query-scopes.ts +5 -8
- package/src/runtime/resolved-numbering-geometry.ts +37 -6
- package/src/runtime/revision-runtime.ts +27 -1
- package/src/runtime/selection/post-edit-validator.ts +60 -6
- package/src/runtime/structure-ops/index.ts +20 -4
- package/src/runtime/surface-projection.ts +290 -21
- package/src/runtime/table-schema.ts +6 -0
- package/src/runtime/theme-color-resolver.ts +2 -2
- package/src/runtime/units.ts +9 -0
- package/src/runtime/workflow-rail-segments.ts +4 -0
- package/src/ui/WordReviewEditor.tsx +187 -43
- package/src/ui/editor-runtime-boundary.ts +10 -0
- package/src/ui/editor-shell-view.tsx +4 -1
- package/src/ui/headless/chrome-registry.ts +53 -0
- package/src/ui/headless/selection-tool-resolver.ts +11 -1
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +13 -0
- package/src/ui-tailwind/chrome/tw-command-palette-mount.tsx +96 -0
- package/src/ui-tailwind/chrome/tw-context-menu.tsx +2 -1
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +5 -4
- package/src/ui-tailwind/chrome/tw-mode-dock.tsx +6 -2
- package/src/ui-tailwind/chrome/use-container-breakpoint.ts +111 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +0 -9
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +1 -0
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +6 -7
- package/src/ui-tailwind/editor-surface/pm-schema.ts +87 -25
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +9 -0
- package/src/ui-tailwind/editor-surface/shape-renderer.ts +76 -14
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +18 -1
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +2 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +18 -2
- package/src/ui-tailwind/index.ts +9 -0
- package/src/ui-tailwind/page-chrome-model.ts +77 -5
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +56 -1
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +2 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +116 -113
- package/src/ui-tailwind/review/tw-review-rail-footer.tsx +2 -2
- package/src/ui-tailwind/theme/tokens.ts +14 -0
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +5 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +29 -87
- package/src/validation/diagnostics.ts +1 -0
|
@@ -157,6 +157,16 @@ function CommentThreadCard(props: {
|
|
|
157
157
|
const canEdit = isOwnComment && thread.status === "open" && props.onEditBody != null;
|
|
158
158
|
const hasNoBody = isEmptyCommentBody(leadEntry?.body);
|
|
159
159
|
const showExcerpt = Boolean(thread.excerpt) && !isDraftThread && thread.excerpt !== "Empty thread";
|
|
160
|
+
const threadCardClassName = [
|
|
161
|
+
"rounded-lg bg-surface/90 transition-colors ring-1 ring-border",
|
|
162
|
+
isActive
|
|
163
|
+
? "bg-accent-soft/40 ring-accent/25 shadow-[var(--shadow-soft)]"
|
|
164
|
+
: "hover:bg-surface",
|
|
165
|
+
thread.status === "detached"
|
|
166
|
+
? "border-l-[3px] border-[var(--color-semantic-warning)] opacity-70"
|
|
167
|
+
: "",
|
|
168
|
+
].join(" ");
|
|
169
|
+
const threadContentPaddingClass = thread.status === "detached" ? "pl-2.5 pr-3" : "px-3";
|
|
160
170
|
|
|
161
171
|
const scrollRef = useCallback(
|
|
162
172
|
(node: HTMLButtonElement | null) => {
|
|
@@ -168,130 +178,123 @@ function CommentThreadCard(props: {
|
|
|
168
178
|
);
|
|
169
179
|
|
|
170
180
|
return (
|
|
171
|
-
<
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
"w-full
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
: "",
|
|
185
|
-
].join(" ")}
|
|
186
|
-
onClick={() => props.onOpenComment?.(thread)}
|
|
187
|
-
>
|
|
188
|
-
{/* Header row: avatar + author + date + status */}
|
|
189
|
-
<div className="mb-1.5 flex items-center gap-1.5">
|
|
190
|
-
<span className="inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-subtle text-[8px] font-semibold text-secondary">
|
|
191
|
-
{thread.createdBy.charAt(0).toUpperCase()}
|
|
192
|
-
</span>
|
|
193
|
-
<span className="truncate text-[10px] font-medium text-primary">{thread.createdBy}</span>
|
|
194
|
-
{thread.status === "detached" && (
|
|
195
|
-
<span
|
|
196
|
-
data-comment-thread-detached-chip="true"
|
|
197
|
-
className="inline-flex items-center rounded-full bg-[var(--color-semantic-warning-soft)] text-[var(--color-semantic-warning)] text-[9px] font-semibold uppercase tracking-[0.08em] px-1.5 py-0.5 ml-1.5"
|
|
198
|
-
>
|
|
199
|
-
Detached
|
|
181
|
+
<div data-comment-thread-card={thread.commentId} className={threadCardClassName}>
|
|
182
|
+
<button
|
|
183
|
+
type="button"
|
|
184
|
+
ref={scrollRef}
|
|
185
|
+
data-comment-thread-id={thread.commentId}
|
|
186
|
+
data-comment-thread-status={thread.status}
|
|
187
|
+
className={["w-full cursor-pointer pb-1 pt-2.5 text-left", threadContentPaddingClass, focusRingClass].join(" ")}
|
|
188
|
+
onClick={() => props.onOpenComment?.(thread)}
|
|
189
|
+
>
|
|
190
|
+
{/* Header row: avatar + author + date + status */}
|
|
191
|
+
<div className="mb-1.5 flex items-center gap-1.5">
|
|
192
|
+
<span className="inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-subtle text-[8px] font-semibold text-secondary">
|
|
193
|
+
{thread.createdBy.charAt(0).toUpperCase()}
|
|
200
194
|
</span>
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
195
|
+
<span className="truncate text-[10px] font-medium text-primary">{thread.createdBy}</span>
|
|
196
|
+
{thread.status === "detached" && (
|
|
197
|
+
<span
|
|
198
|
+
data-comment-thread-detached-chip="true"
|
|
199
|
+
className="ml-1.5 inline-flex items-center rounded-full bg-[var(--color-semantic-warning-soft)] px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-[0.08em] text-[var(--color-semantic-warning)]"
|
|
200
|
+
>
|
|
201
|
+
Detached
|
|
202
|
+
</span>
|
|
203
|
+
)}
|
|
204
|
+
<span data-comment-thread-created-at="true" className="text-[9px] text-tertiary">
|
|
205
|
+
{formatCommentDate(thread.createdAt)}
|
|
206
|
+
</span>
|
|
207
|
+
<span className="flex-1" />
|
|
208
|
+
{isDraftThread ? <StatusBadge label="draft" tone="draft" /> : null}
|
|
209
|
+
{thread.status === "resolved" ? <StatusBadge label="resolved" tone="resolved" /> : null}
|
|
210
|
+
</div>
|
|
209
211
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
212
|
+
{/* Excerpt — anchored text from document */}
|
|
213
|
+
{showExcerpt ? (
|
|
214
|
+
<p className="mb-1.5 rounded-md bg-comment-soft px-2 py-1 text-[9px] italic leading-4 text-secondary whitespace-pre-wrap break-words line-clamp-2">
|
|
215
|
+
{thread.excerpt}
|
|
216
|
+
</p>
|
|
217
|
+
) : null}
|
|
216
218
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
219
|
+
{/* Comment body */}
|
|
220
|
+
{canEdit && (isActive || hasNoBody) ? (
|
|
221
|
+
<InlineEditableBody
|
|
222
|
+
body={leadEntry?.body ?? ""}
|
|
223
|
+
autoFocus={isActive && hasNoBody}
|
|
224
|
+
onSave={(newBody) => props.onEditBody?.(thread.commentId, newBody)}
|
|
225
|
+
label={isDraftThread ? "New comment" : undefined}
|
|
226
|
+
/>
|
|
227
|
+
) : presentation ? (
|
|
228
|
+
<CommentMarkdownRenderer
|
|
229
|
+
body={presentation.body}
|
|
230
|
+
mentions={presentation.mentions}
|
|
231
|
+
attachments={presentation.attachments}
|
|
232
|
+
resolveAttachmentHref={resolveAttachmentHref}
|
|
233
|
+
className="text-[10px] leading-[1.1rem] text-secondary break-words"
|
|
234
|
+
/>
|
|
235
|
+
) : leadEntry?.body ? (
|
|
236
|
+
<p
|
|
237
|
+
className="text-[10px] leading-[1.1rem] text-secondary whitespace-pre-wrap break-words line-clamp-4"
|
|
238
|
+
data-comment-thread-body="true"
|
|
239
|
+
>
|
|
240
|
+
{leadEntry.body}
|
|
241
|
+
</p>
|
|
242
|
+
) : canEdit ? (
|
|
243
|
+
<p
|
|
244
|
+
className="cursor-text text-[10px] italic text-tertiary"
|
|
245
|
+
onClick={(e) => {
|
|
246
|
+
e.stopPropagation();
|
|
247
|
+
props.onOpenComment?.(thread);
|
|
248
|
+
}}
|
|
249
|
+
>
|
|
250
|
+
New comment
|
|
251
|
+
</p>
|
|
252
|
+
) : null}
|
|
251
253
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
254
|
+
{/* Reply entries (compact) */}
|
|
255
|
+
{thread.entries.slice(1).map((entry) => {
|
|
256
|
+
const replyPresentation = replyPresentationByEntryId.get(entry.entryId);
|
|
257
|
+
return (
|
|
258
|
+
<div key={entry.entryId} className="mt-2 ml-4 border-l border-border pl-2.5">
|
|
259
|
+
<div className="mb-0.5 flex items-center gap-1">
|
|
260
|
+
<span className="text-[9px] font-medium text-secondary">{entry.authorId}</span>
|
|
261
|
+
<span className="text-[9px] text-tertiary">{formatCommentDate(entry.createdAt)}</span>
|
|
262
|
+
</div>
|
|
263
|
+
{replyPresentation ? (
|
|
264
|
+
<CommentMarkdownRenderer
|
|
265
|
+
body={replyPresentation.body}
|
|
266
|
+
mentions={presentation?.mentions}
|
|
267
|
+
attachments={presentation?.attachments}
|
|
268
|
+
resolveAttachmentHref={resolveAttachmentHref}
|
|
269
|
+
className="text-[10px] leading-4 text-secondary break-words"
|
|
270
|
+
/>
|
|
271
|
+
) : (
|
|
272
|
+
<p
|
|
273
|
+
className="text-[10px] leading-4 text-secondary whitespace-pre-wrap break-words line-clamp-3"
|
|
274
|
+
data-comment-reply-body="true"
|
|
275
|
+
>
|
|
276
|
+
{entry.body}
|
|
277
|
+
</p>
|
|
278
|
+
)}
|
|
260
279
|
</div>
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
body={replyPresentation.body}
|
|
264
|
-
mentions={presentation?.mentions}
|
|
265
|
-
attachments={presentation?.attachments}
|
|
266
|
-
resolveAttachmentHref={resolveAttachmentHref}
|
|
267
|
-
className="text-[10px] leading-4 text-secondary break-words"
|
|
268
|
-
/>
|
|
269
|
-
) : (
|
|
270
|
-
<p
|
|
271
|
-
className="text-[10px] leading-4 text-secondary whitespace-pre-wrap break-words line-clamp-3"
|
|
272
|
-
data-comment-reply-body="true"
|
|
273
|
-
>
|
|
274
|
-
{entry.body}
|
|
275
|
-
</p>
|
|
276
|
-
)}
|
|
277
|
-
</div>
|
|
278
|
-
);
|
|
279
|
-
})}
|
|
280
|
+
);
|
|
281
|
+
})}
|
|
280
282
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
283
|
+
{thread.entryCount > thread.entries.length ? (
|
|
284
|
+
<p className="mt-1 text-[9px] text-tertiary">
|
|
285
|
+
+{thread.entryCount - thread.entries.length} more
|
|
286
|
+
</p>
|
|
287
|
+
) : null}
|
|
288
|
+
</button>
|
|
286
289
|
|
|
287
290
|
{/* Inline actions — compact, horizontal */}
|
|
288
|
-
<div className="mt-2 flex items-center gap-1">
|
|
291
|
+
<div className={["mt-2 flex items-center gap-1 pb-2.5", threadContentPaddingClass].join(" ")}>
|
|
289
292
|
{thread.status === "open" && (
|
|
290
293
|
<>
|
|
291
294
|
<button
|
|
292
295
|
type="button"
|
|
293
296
|
className="inline-flex items-center gap-0.5 rounded px-1 py-0.5 text-[9px] font-medium text-accent hover:bg-accent-soft transition-colors"
|
|
294
|
-
onClick={(
|
|
297
|
+
onClick={() => props.onResolveComment?.(thread.commentId)}
|
|
295
298
|
>
|
|
296
299
|
<Check className="h-2 w-2" /> Resolve
|
|
297
300
|
</button>
|
|
@@ -305,7 +308,7 @@ function CommentThreadCard(props: {
|
|
|
305
308
|
type="button"
|
|
306
309
|
className="inline-flex items-center gap-0.5 rounded px-1 py-0.5 text-[9px] font-medium text-secondary hover:bg-surface-hover transition-colors"
|
|
307
310
|
data-comment-thread-action="reopen"
|
|
308
|
-
onClick={(
|
|
311
|
+
onClick={() => props.onReopenComment?.(thread.commentId)}
|
|
309
312
|
>
|
|
310
313
|
<RotateCcw className="h-2 w-2" /> Reopen
|
|
311
314
|
</button>
|
|
@@ -314,7 +317,7 @@ function CommentThreadCard(props: {
|
|
|
314
317
|
<span className="text-[9px] text-comment">Detached</span>
|
|
315
318
|
)}
|
|
316
319
|
</div>
|
|
317
|
-
</
|
|
320
|
+
</div>
|
|
318
321
|
);
|
|
319
322
|
}
|
|
320
323
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { HelpCircle, Search } from "lucide-react";
|
|
3
|
+
import { FOCUS_RING_CLASSES } from "../theme/tokens";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Thin pinned footer rendered at the bottom of the review rail. The footer
|
|
@@ -14,8 +15,7 @@ export interface TwReviewRailFooterProps {
|
|
|
14
15
|
searchLabel?: string;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
const focusRingClass =
|
|
18
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 focus-visible:ring-offset-canvas";
|
|
18
|
+
const focusRingClass = FOCUS_RING_CLASSES;
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Accept only http(s) and mailto help links. Rejects javascript:, data:,
|
|
@@ -273,6 +273,20 @@ export const HOST_LOCKED_TOKENS = [
|
|
|
273
273
|
export type HostOverridableToken = (typeof HOST_OVERRIDABLE_TOKENS)[number];
|
|
274
274
|
export type HostLockedToken = (typeof HOST_LOCKED_TOKENS)[number];
|
|
275
275
|
|
|
276
|
+
/**
|
|
277
|
+
* Canonical focus-visible ring class string (designsystem §4.7 / §7.2).
|
|
278
|
+
*
|
|
279
|
+
* Every interactive chrome surface that renders a custom focus indicator
|
|
280
|
+
* MUST import this constant rather than inline the Tailwind utilities.
|
|
281
|
+
* The invariant is enforced by `test/ui-tailwind/focus-ring-canonical.test.ts`.
|
|
282
|
+
*
|
|
283
|
+
* Exceptions: input / textarea elements that use a 1-px border ring as the
|
|
284
|
+
* idle-state affordance (e.g. `tw-comment-sidebar` reply composer) are
|
|
285
|
+
* a distinct pattern and not covered by this utility.
|
|
286
|
+
*/
|
|
287
|
+
export const FOCUS_RING_CLASSES =
|
|
288
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-canvas";
|
|
289
|
+
|
|
276
290
|
/** Returns true if `path` is in the locked set (must not be overridden by hosts). */
|
|
277
291
|
export function isTokenPathLocked(path: string): boolean {
|
|
278
292
|
return (HOST_LOCKED_TOKENS as readonly string[]).includes(path);
|
|
@@ -131,6 +131,11 @@ export function TwShellHeader(props: TwShellHeaderProps): React.ReactElement {
|
|
|
131
131
|
key={mode.id}
|
|
132
132
|
value={mode.id}
|
|
133
133
|
disabled={mode.disabled}
|
|
134
|
+
onClick={() => {
|
|
135
|
+
if (!mode.disabled) {
|
|
136
|
+
props.onModeChange?.(mode.id);
|
|
137
|
+
}
|
|
138
|
+
}}
|
|
134
139
|
className={`wre-rail-tab ${focusRingClass}`}
|
|
135
140
|
data-testid={`tw-shell-header__mode-${mode.id}`}
|
|
136
141
|
>
|
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
ActiveListContext,
|
|
18
18
|
CommentSidebarThreadSnapshot,
|
|
19
19
|
DocumentNavigationSnapshot,
|
|
20
|
+
EditorAnchorProjection,
|
|
20
21
|
EditorStoryTarget,
|
|
21
22
|
EditorViewStateSnapshot,
|
|
22
23
|
FormattingStateSnapshot,
|
|
@@ -39,15 +40,8 @@ import type {
|
|
|
39
40
|
WorkspaceMode,
|
|
40
41
|
ZoomLevel,
|
|
41
42
|
} from "../api/public-types";
|
|
42
|
-
import { findPageForOffset } from "../runtime/document-navigation.ts";
|
|
43
43
|
import { createCanvasBackend } from "../runtime/layout/index.ts";
|
|
44
|
-
import {
|
|
45
|
-
DEFAULT_PAGE_ESTIMATE_PX_PER_TWIP,
|
|
46
|
-
estimateBlockHeight,
|
|
47
|
-
estimateParagraphLineCount,
|
|
48
|
-
estimateParagraphLineHeight,
|
|
49
|
-
getUsableColumnWidth,
|
|
50
|
-
} from "../runtime/page-layout-estimation.ts";
|
|
44
|
+
import { DEFAULT_PAGE_ESTIMATE_PX_PER_TWIP } from "../runtime/page-layout-estimation.ts";
|
|
51
45
|
import {
|
|
52
46
|
incrementInvalidationCounter,
|
|
53
47
|
recordPerfSample,
|
|
@@ -96,7 +90,7 @@ import { resolveSelectionToolPlacement } from "./chrome/tw-selection-tool-placem
|
|
|
96
90
|
import { TwReviewRail, type ReviewRailTab } from "./review/tw-review-rail";
|
|
97
91
|
import { TwStatusBar } from "./status/tw-status-bar";
|
|
98
92
|
import { type ToolbarInteractionPolicy } from "./toolbar/tw-toolbar";
|
|
99
|
-
import { TwChromeOverlay } from "./chrome-overlay";
|
|
93
|
+
import { TwChromeOverlay, TwPageStackOverlayLayer } from "./chrome-overlay";
|
|
100
94
|
import {
|
|
101
95
|
cycleScopeIndex,
|
|
102
96
|
shouldHandleScopeNavKey,
|
|
@@ -423,6 +417,7 @@ export interface TwReviewWorkspaceProps {
|
|
|
423
417
|
*/
|
|
424
418
|
onScopeAskAgent?: (payload: {
|
|
425
419
|
scopeId: string;
|
|
420
|
+
anchor?: EditorAnchorProjection;
|
|
426
421
|
}) => void;
|
|
427
422
|
/**
|
|
428
423
|
* P3 — optional scope-tag editor slot rendered inside the scope
|
|
@@ -538,9 +533,12 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
|
|
|
538
533
|
const onScopeAskAgent = props.onScopeAskAgent;
|
|
539
534
|
const handleScopeCardAskAgent = useCallback(
|
|
540
535
|
(scopeId: string) => {
|
|
541
|
-
|
|
536
|
+
const cardModel = props.layoutFacet
|
|
537
|
+
?.getAllScopeCardModels?.()
|
|
538
|
+
?.find((m) => m.scopeId === scopeId);
|
|
539
|
+
onScopeAskAgent?.({ scopeId, anchor: cardModel?.anchor });
|
|
542
540
|
},
|
|
543
|
-
[onScopeAskAgent],
|
|
541
|
+
[onScopeAskAgent, props.layoutFacet],
|
|
544
542
|
);
|
|
545
543
|
const zoomLevel = props.zoomLevel ?? 100;
|
|
546
544
|
// Numeric zooms resolve immediately; "pageWidth" / "onePage" need the
|
|
@@ -548,7 +546,7 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
|
|
|
548
546
|
// `pageShellMetrics` has been computed (P2.c).
|
|
549
547
|
const numericZoomScale = typeof zoomLevel === "number" ? zoomLevel / 100 : 1;
|
|
550
548
|
const chromePreset = resolveChromePreset(props.chromePreset, props.reviewMode);
|
|
551
|
-
const chromeOptions = resolveChromePresetOptions(chromePreset, props.chromeOptions);
|
|
549
|
+
const chromeOptions = resolveChromePresetOptions(chromePreset, props.chromeOptions, viewState.editorRole);
|
|
552
550
|
const preserveOnlyCount = caps?.preserveOnlyCount ??
|
|
553
551
|
snapshot.compatibility.featureEntries.filter(
|
|
554
552
|
(entry) => entry.featureClass === "preserve-only",
|
|
@@ -1606,12 +1604,12 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
|
|
|
1606
1604
|
style={
|
|
1607
1605
|
isPageWorkspace
|
|
1608
1606
|
? {
|
|
1609
|
-
//
|
|
1610
|
-
//
|
|
1611
|
-
//
|
|
1612
|
-
//
|
|
1613
|
-
//
|
|
1614
|
-
//
|
|
1607
|
+
// N1 (L8 Phase D): paper chrome (bg/border/shadow)
|
|
1608
|
+
// now lives on per-page overlay cards (z-0) painted
|
|
1609
|
+
// by TwPageStackOverlayLayer below. This wrapper
|
|
1610
|
+
// keeps width/height/zoom so layout measurement stays
|
|
1611
|
+
// truthful but is visually transparent so the canvas
|
|
1612
|
+
// background shows through inter-page gaps.
|
|
1615
1613
|
...(pageShellMetrics.frameWidthPx
|
|
1616
1614
|
? { width: `${pageShellMetrics.frameWidthPx}px` }
|
|
1617
1615
|
: {}),
|
|
@@ -1619,11 +1617,23 @@ export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
|
|
|
1619
1617
|
? { minHeight: `${pageShellMetrics.frameHeightPx}px` }
|
|
1620
1618
|
: {}),
|
|
1621
1619
|
...(zoomScale !== 1 ? { zoom: zoomScale } : {}),
|
|
1622
|
-
...pageShellMetrics.pageFrameStyle,
|
|
1623
1620
|
}
|
|
1624
1621
|
: undefined
|
|
1625
1622
|
}
|
|
1626
1623
|
>
|
|
1624
|
+
{/* N1 (L8 Phase D): per-page paper card backgrounds at z-0,
|
|
1625
|
+
painted before the z-10 PM wrapper so white card areas
|
|
1626
|
+
sit behind PM text. Gaps between cards expose the gray
|
|
1627
|
+
workspace-canvas background, giving N discrete papers. */}
|
|
1628
|
+
{isPageWorkspace && chromeVisibility.pageChrome && props.layoutFacet ? (
|
|
1629
|
+
<TwPageStackOverlayLayer
|
|
1630
|
+
facet={props.layoutFacet}
|
|
1631
|
+
scrollRoot={pageStackScrollRoot}
|
|
1632
|
+
renderFrameRevision={renderFrameRevision}
|
|
1633
|
+
visiblePageIndexRange={visiblePageIndexRange}
|
|
1634
|
+
data-layer="page-card-backgrounds"
|
|
1635
|
+
/>
|
|
1636
|
+
) : null}
|
|
1627
1637
|
{isPageWorkspace && chromeVisibility.pageChrome && pageChromeModel.lineNumberingEnabled ? (
|
|
1628
1638
|
<div
|
|
1629
1639
|
aria-hidden="true"
|
|
@@ -1990,7 +2000,6 @@ const EMPTY_PAGE_CHROME_MODEL: PageChromeModel = {
|
|
|
1990
2000
|
documentGridStyle: undefined,
|
|
1991
2001
|
};
|
|
1992
2002
|
|
|
1993
|
-
const DOCUMENT_CONTENT_TOP_PADDING_PX = 40;
|
|
1994
2003
|
|
|
1995
2004
|
// P2.a — real-dimension page frame. Page frame width/height are
|
|
1996
2005
|
// `pageLayout.pageWidth/pageHeight × FRAME_PX_PER_TWIP_AT_96DPI` so
|
|
@@ -2024,7 +2033,6 @@ function buildPageChromeModel(
|
|
|
2024
2033
|
pageLayout,
|
|
2025
2034
|
surfaceBlocks: surface.blocks,
|
|
2026
2035
|
pages: navigation.pages,
|
|
2027
|
-
buildLineNumberMarkers,
|
|
2028
2036
|
});
|
|
2029
2037
|
const lineNumberingEnabled =
|
|
2030
2038
|
Boolean(pageLayout.lineNumbering) && lineMarkers.length > 0;
|
|
@@ -2124,72 +2132,6 @@ export function resolveZoomMultiplier(
|
|
|
2124
2132
|
);
|
|
2125
2133
|
}
|
|
2126
2134
|
|
|
2127
|
-
function buildLineNumberMarkers(
|
|
2128
|
-
blocks: readonly SurfaceBlockSnapshot[],
|
|
2129
|
-
pages: ReadonlyArray<DocumentNavigationSnapshot["pages"][number]>,
|
|
2130
|
-
): Array<{ id: string; label: string; topPx: number }> {
|
|
2131
|
-
const markers: Array<{ id: string; label: string; topPx: number }> = [];
|
|
2132
|
-
if (pages.length === 0) {
|
|
2133
|
-
return markers;
|
|
2134
|
-
}
|
|
2135
|
-
|
|
2136
|
-
let currentTopTwips = 0;
|
|
2137
|
-
let lineNumber = 1;
|
|
2138
|
-
let lastPageIndex = -1;
|
|
2139
|
-
let lastSectionIndex = -1;
|
|
2140
|
-
|
|
2141
|
-
for (const block of blocks) {
|
|
2142
|
-
const pageIndex = findPageForOffset(pages, block.from);
|
|
2143
|
-
const page = pages[pageIndex];
|
|
2144
|
-
if (!page) {
|
|
2145
|
-
continue;
|
|
2146
|
-
}
|
|
2147
|
-
|
|
2148
|
-
const lineNumbering = page.layout.lineNumbering;
|
|
2149
|
-
const restartMode = lineNumbering?.restart ?? "newPage";
|
|
2150
|
-
const restartStart = lineNumbering?.start ?? 1;
|
|
2151
|
-
const countBy = Math.max(1, lineNumbering?.countBy ?? 1);
|
|
2152
|
-
const columnWidth = getUsableColumnWidth(page.layout);
|
|
2153
|
-
|
|
2154
|
-
if (pageIndex !== lastPageIndex) {
|
|
2155
|
-
if (restartMode === "newPage" || lastPageIndex === -1) {
|
|
2156
|
-
lineNumber = restartStart;
|
|
2157
|
-
}
|
|
2158
|
-
lastPageIndex = pageIndex;
|
|
2159
|
-
}
|
|
2160
|
-
if (page.sectionIndex !== lastSectionIndex) {
|
|
2161
|
-
if (restartMode === "newSection" || lastSectionIndex === -1) {
|
|
2162
|
-
lineNumber = restartStart;
|
|
2163
|
-
}
|
|
2164
|
-
lastSectionIndex = page.sectionIndex;
|
|
2165
|
-
}
|
|
2166
|
-
|
|
2167
|
-
if (block.kind === "paragraph" && lineNumbering) {
|
|
2168
|
-
const lineCount = estimateParagraphLineCount(block, columnWidth);
|
|
2169
|
-
const lineHeight = estimateParagraphLineHeight(block);
|
|
2170
|
-
const suppress = block.suppressLineNumbers === true;
|
|
2171
|
-
for (let lineIndex = 0; lineIndex < lineCount; lineIndex += 1) {
|
|
2172
|
-
if (!suppress && (lineNumber - restartStart) % countBy === 0) {
|
|
2173
|
-
markers.push({
|
|
2174
|
-
id: `${block.blockId}-${lineIndex}`,
|
|
2175
|
-
label: String(lineNumber),
|
|
2176
|
-
topPx:
|
|
2177
|
-
DOCUMENT_CONTENT_TOP_PADDING_PX +
|
|
2178
|
-
(currentTopTwips + lineIndex * lineHeight) * DEFAULT_PAGE_ESTIMATE_PX_PER_TWIP,
|
|
2179
|
-
});
|
|
2180
|
-
}
|
|
2181
|
-
if (!suppress) {
|
|
2182
|
-
lineNumber += 1;
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
2185
|
-
}
|
|
2186
|
-
|
|
2187
|
-
currentTopTwips += estimateBlockHeight(block, columnWidth);
|
|
2188
|
-
}
|
|
2189
|
-
|
|
2190
|
-
return markers;
|
|
2191
|
-
}
|
|
2192
|
-
|
|
2193
2135
|
function shouldRenderPageBorder(
|
|
2194
2136
|
pageLayout: RuntimeRenderSnapshot["pageLayout"],
|
|
2195
2137
|
pages: ReadonlyArray<DocumentNavigationSnapshot["pages"][number]>,
|