@beyondwork/docx-react-component 1.0.31 → 1.0.33

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.
@@ -3,7 +3,6 @@ import type {
3
3
  CommentSidebarThreadSnapshot,
4
4
  FormattingStateSnapshot,
5
5
  InteractionGuardSnapshot,
6
- StyleCatalogSnapshot,
7
6
  TableStructureContextSnapshot,
8
7
  SuggestionsSnapshot,
9
8
  TrackedChangeEntrySnapshot,
@@ -79,7 +78,7 @@ export type StructureContextKind = "table" | "image" | "object" | "list";
79
78
  export interface StructureContextSelectionToolModel extends BaseSelectionToolModel {
80
79
  kind: "structure-context";
81
80
  structureKind: StructureContextKind;
82
- tableStyles?: StyleCatalogSnapshot["tables"];
81
+ tableStyles?: Array<{ styleId: string; displayName: string }>;
83
82
  activeTable?: TableStructureContextSnapshot | null;
84
83
  activeImage?: ActiveImageContext;
85
84
  activeObject?: ActiveObjectContext;
@@ -3,6 +3,7 @@ import type {
3
3
  RuntimeRenderSnapshot,
4
4
  SurfaceBlockSnapshot,
5
5
  WorkflowBlockedCommandReason,
6
+ WorkflowLockedZone,
6
7
  WorkflowMarkupSnapshot,
7
8
  } from "../api/public-types";
8
9
 
@@ -10,6 +11,21 @@ export function deriveVisibleWorkflowBlockedRails(
10
11
  surface: RuntimeRenderSnapshot["surface"] | undefined,
11
12
  markupSnapshot: WorkflowMarkupSnapshot | null,
12
13
  ): WorkflowBlockedCommandReason[] {
14
+ return deriveVisibleWorkflowLockedZones(surface, markupSnapshot).map((zone) => ({
15
+ code: zone.code,
16
+ message: zone.detail,
17
+ fragmentId: zone.fragmentId,
18
+ label: zone.label,
19
+ detail: zone.detail,
20
+ anchor: zone.anchor,
21
+ storyTarget: zone.storyTarget,
22
+ }));
23
+ }
24
+
25
+ export function deriveVisibleWorkflowLockedZones(
26
+ surface: RuntimeRenderSnapshot["surface"] | undefined,
27
+ markupSnapshot: WorkflowMarkupSnapshot | null,
28
+ ): WorkflowLockedZone[] {
13
29
  if (!surface || !markupSnapshot) {
14
30
  return [];
15
31
  }
@@ -22,8 +38,10 @@ export function deriveVisibleWorkflowBlockedRails(
22
38
  ),
23
39
  )
24
40
  .map((fragment) => ({
41
+ fragmentId: fragment.fragmentId,
25
42
  code: fragment.blockedReasonCode,
26
- message: fragment.detail,
43
+ label: fragment.label,
44
+ detail: fragment.detail,
27
45
  anchor: fragment.anchor,
28
46
  storyTarget: fragment.storyTarget,
29
47
  }));
@@ -52,8 +52,7 @@ export function TwSelectionToolStructure(props: TwSelectionToolStructureProps) {
52
52
  return (
53
53
  <TwTableContextToolbar
54
54
  disabled={!props.model.canMutate}
55
- tableContext={props.model.activeTable ?? null}
56
- tableStyles={props.model.tableStyles ?? []}
55
+ tableContext={props.model.activeTable ?? null} tableStyles={props.model.tableStyles ?? []}
57
56
  onSetTableStyle={props.onSetTableStyle}
58
57
  onAddRowBefore={props.onAddRowBefore}
59
58
  onAddRowAfter={props.onAddRowAfter}
@@ -1,7 +1,6 @@
1
1
  import React from "react";
2
2
 
3
3
  import type {
4
- StyleCatalogSnapshot,
5
4
  TableOperationCapabilitySnapshot,
6
5
  TableStructureContextSnapshot,
7
6
  } from "../../api/public-types";
@@ -10,7 +9,7 @@ import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-edi
10
9
  export interface TwTableContextToolbarProps {
11
10
  disabled: boolean;
12
11
  tableContext: TableStructureContextSnapshot | null;
13
- tableStyles: StyleCatalogSnapshot["tables"];
12
+ tableStyles: Array<{ styleId: string; displayName: string }>;
14
13
  onSetTableStyle?: (styleId: string) => void;
15
14
  onAddRowBefore?: () => void;
16
15
  onAddRowAfter?: () => void;
@@ -9,6 +9,7 @@ import type {
9
9
  EditorStoryTarget,
10
10
  WorkflowBlockedCommandReason,
11
11
  WorkflowCandidateRange,
12
+ WorkflowLockedZone,
12
13
  WorkflowMetadataMarkup,
13
14
  WorkflowScope,
14
15
  } from "../../api/public-types";
@@ -81,16 +82,16 @@ function getWorkflowMetadataInlineClass(): string {
81
82
 
82
83
  function getWorkflowBlockedInlineClass(reason: WorkflowBlockedCommandReason): string {
83
84
  if (reason.code === "workflow_blocked_import") {
84
- return "wre-workflow-inline wre-workflow-inline-blocked-import";
85
+ return "wre-workflow-inline wre-workflow-inline-locked-zone wre-workflow-inline-blocked-import";
85
86
  }
86
- return "wre-workflow-inline wre-workflow-inline-preserve-only";
87
+ return "wre-workflow-inline wre-workflow-inline-locked-zone wre-workflow-inline-preserve-only";
87
88
  }
88
89
 
89
90
  function getWorkflowBlockedRailClass(reason: WorkflowBlockedCommandReason): string {
90
91
  if (reason.code === "workflow_blocked_import") {
91
- return "wre-workflow-rail wre-workflow-rail-blocked-import";
92
+ return "wre-workflow-rail wre-workflow-rail-locked-zone wre-workflow-rail-blocked-import";
92
93
  }
93
- return "wre-workflow-rail wre-workflow-rail-preserve-only";
94
+ return "wre-workflow-rail wre-workflow-rail-locked-zone wre-workflow-rail-preserve-only";
94
95
  }
95
96
 
96
97
  function hasBlockChildren(node: PMNode): boolean {
@@ -172,6 +173,56 @@ function buildAnchorPmRange(
172
173
  };
173
174
  }
174
175
 
176
+ function collectLockedPmRanges(
177
+ lockedZones: readonly WorkflowLockedZone[] | undefined,
178
+ activeStory: EditorStoryTarget,
179
+ positionMap: PositionMap,
180
+ ): Array<{ from: number; to: number; zone: WorkflowLockedZone }> {
181
+ if (!lockedZones || lockedZones.length === 0) {
182
+ return [];
183
+ }
184
+ const ranges: Array<{ from: number; to: number; zone: WorkflowLockedZone }> = [];
185
+ for (const zone of lockedZones) {
186
+ const zoneStoryTarget = zone.storyTarget ?? MAIN_STORY_TARGET;
187
+ if (!storyTargetsEqual(zoneStoryTarget, activeStory)) {
188
+ continue;
189
+ }
190
+ const pmRange = buildAnchorPmRange(zone.anchor, positionMap);
191
+ if (!pmRange || !pmRange.allowInline || pmRange.from >= pmRange.to) {
192
+ continue;
193
+ }
194
+ ranges.push({ from: pmRange.from, to: pmRange.to, zone });
195
+ }
196
+ return ranges;
197
+ }
198
+
199
+ function subtractInlineOverlaps(
200
+ baseRange: { from: number; to: number },
201
+ blockedRanges: Array<{ from: number; to: number }>,
202
+ ): Array<{ from: number; to: number }> {
203
+ let segments = [baseRange];
204
+ for (const blockedRange of blockedRanges) {
205
+ const nextSegments: Array<{ from: number; to: number }> = [];
206
+ for (const segment of segments) {
207
+ if (blockedRange.to <= segment.from || blockedRange.from >= segment.to) {
208
+ nextSegments.push(segment);
209
+ continue;
210
+ }
211
+ if (blockedRange.from > segment.from) {
212
+ nextSegments.push({ from: segment.from, to: blockedRange.from });
213
+ }
214
+ if (blockedRange.to < segment.to) {
215
+ nextSegments.push({ from: blockedRange.to, to: segment.to });
216
+ }
217
+ }
218
+ segments = nextSegments;
219
+ if (segments.length === 0) {
220
+ break;
221
+ }
222
+ }
223
+ return segments.filter((segment) => segment.from < segment.to);
224
+ }
225
+
175
226
  function pushRailDecorations(
176
227
  decorations: Decoration[],
177
228
  doc: PMNode,
@@ -210,10 +261,12 @@ export function buildDecorations(
210
261
  revisionModel: RevisionDecorationModel | undefined,
211
262
  markupDisplay: MarkupDisplay,
212
263
  showTrackedChanges = true,
264
+ suggestionsEnabled = false,
213
265
  workflowScopes?: readonly WorkflowScope[],
214
266
  activeStory: EditorStoryTarget = MAIN_STORY_TARGET,
215
267
  workflowCandidates?: readonly WorkflowCandidateRange[],
216
268
  workflowBlockedReasons?: readonly WorkflowBlockedCommandReason[],
269
+ workflowLockedZones?: readonly WorkflowLockedZone[],
217
270
  activeWorkflowWorkItemId?: string | null,
218
271
  activeWorkflowScopeIds?: readonly string[],
219
272
  workflowMetadata?: readonly WorkflowMetadataMarkup[],
@@ -221,6 +274,7 @@ export function buildDecorations(
221
274
  const decorations: Decoration[] = [];
222
275
  const railRangeCache = new Map<string, Array<{ from: number; to: number }>>();
223
276
  const activeScopeIds = new Set(activeWorkflowScopeIds ?? []);
277
+ const lockedPmRanges = collectLockedPmRanges(workflowLockedZones, activeStory, positionMap);
224
278
 
225
279
  // Walk comment threads and create inline decorations
226
280
  if (commentModel) {
@@ -271,6 +325,47 @@ export function buildDecorations(
271
325
  // Skip visual styling when tracked changes display is off
272
326
  if (!showTrackedChanges) continue;
273
327
 
328
+ const pmFrom = positionMap.runtimeToPm(rev.from);
329
+ const pmTo = positionMap.runtimeToPm(rev.to);
330
+ if (pmFrom >= pmTo) continue;
331
+
332
+ if (suggestionsEnabled) {
333
+ if (rev.kind === "insertion") {
334
+ decorations.push(
335
+ Decoration.inline(pmFrom, pmTo, {
336
+ class: "text-insert",
337
+ "data-revision-id": rev.revisionId,
338
+ }),
339
+ );
340
+ decorations.push(
341
+ Decoration.widget(pmFrom, () => {
342
+ const el = document.createElement("span");
343
+ el.textContent = "[";
344
+ el.className = "text-insert";
345
+ el.setAttribute("contenteditable", "false");
346
+ return el;
347
+ }, { side: -1, key: `${rev.revisionId}-open` }),
348
+ );
349
+ decorations.push(
350
+ Decoration.widget(pmTo, () => {
351
+ const el = document.createElement("span");
352
+ el.textContent = "]";
353
+ el.className = "text-insert";
354
+ el.setAttribute("contenteditable", "false");
355
+ return el;
356
+ }, { side: 1, key: `${rev.revisionId}-close` }),
357
+ );
358
+ } else if (rev.kind === "deletion") {
359
+ decorations.push(
360
+ Decoration.inline(pmFrom, pmTo, {
361
+ class: "text-danger line-through decoration-danger/80 decoration-1",
362
+ "data-revision-id": rev.revisionId,
363
+ }),
364
+ );
365
+ }
366
+ continue;
367
+ }
368
+
274
369
  const cls = getRevisionHighlightClass(
275
370
  revisionModel,
276
371
  rev.from,
@@ -279,16 +374,12 @@ export function buildDecorations(
279
374
  );
280
375
  if (!cls) continue;
281
376
 
282
- const pmFrom = positionMap.runtimeToPm(rev.from);
283
- const pmTo = positionMap.runtimeToPm(rev.to);
284
- if (pmFrom < pmTo) {
285
- decorations.push(
286
- Decoration.inline(pmFrom, pmTo, {
287
- class: cls,
288
- "data-revision-id": rev.revisionId,
289
- }),
290
- );
291
- }
377
+ decorations.push(
378
+ Decoration.inline(pmFrom, pmTo, {
379
+ class: cls,
380
+ "data-revision-id": rev.revisionId,
381
+ }),
382
+ );
292
383
  }
293
384
  }
294
385
 
@@ -307,15 +398,21 @@ export function buildDecorations(
307
398
  );
308
399
 
309
400
  if (pmRange.allowInline && pmRange.from < pmRange.to) {
310
- decorations.push(
311
- Decoration.inline(pmRange.from, pmRange.to, {
312
- class: getWorkflowInlineClass(scope, isActiveWorkItem, isSelectionZone),
313
- "data-workflow-scope-id": scope.scopeId,
314
- "data-workflow-scope-mode": scope.mode,
315
- "data-workflow-active": isActiveWorkItem ? "true" : "false",
316
- ...(isSelectionZone ? { "data-workflow-zone": "selection" } : {}),
317
- }),
401
+ const visibleScopeSegments = subtractInlineOverlaps(
402
+ { from: pmRange.from, to: pmRange.to },
403
+ lockedPmRanges.filter((range) => range.to > pmRange.from && range.from < pmRange.to),
318
404
  );
405
+ for (const visibleSegment of visibleScopeSegments) {
406
+ decorations.push(
407
+ Decoration.inline(visibleSegment.from, visibleSegment.to, {
408
+ class: getWorkflowInlineClass(scope, isActiveWorkItem, isSelectionZone),
409
+ "data-workflow-scope-id": scope.scopeId,
410
+ "data-workflow-scope-mode": scope.mode,
411
+ "data-workflow-active": isActiveWorkItem ? "true" : "false",
412
+ ...(isSelectionZone ? { "data-workflow-zone": "selection" } : {}),
413
+ }),
414
+ );
415
+ }
319
416
  }
320
417
 
321
418
  pushRailDecorations(decorations, doc, pmRange.from, pmRange.to, {
@@ -375,8 +472,19 @@ export function buildDecorations(
375
472
  }
376
473
  }
377
474
 
378
- if (workflowBlockedReasons) {
379
- for (const reason of workflowBlockedReasons) {
475
+ if ((workflowLockedZones && workflowLockedZones.length > 0) || workflowBlockedReasons) {
476
+ const blockedReasonsToRender = workflowLockedZones && workflowLockedZones.length > 0
477
+ ? workflowLockedZones.map((zone) => ({
478
+ code: zone.code,
479
+ message: zone.detail,
480
+ anchor: zone.anchor,
481
+ storyTarget: zone.storyTarget,
482
+ fragmentId: zone.fragmentId,
483
+ label: zone.label,
484
+ detail: zone.detail,
485
+ }))
486
+ : workflowBlockedReasons ?? [];
487
+ for (const reason of blockedReasonsToRender) {
380
488
  if (
381
489
  reason.code !== "workflow_preserve_only" &&
382
490
  reason.code !== "workflow_blocked_import"
@@ -393,6 +501,8 @@ export function buildDecorations(
393
501
  Decoration.inline(pmRange.from, pmRange.to, {
394
502
  class: getWorkflowBlockedInlineClass(reason),
395
503
  "data-workflow-blocked-code": reason.code,
504
+ ...(reason.fragmentId ? { "data-workflow-fragment-id": reason.fragmentId } : {}),
505
+ "data-workflow-zone": "locked",
396
506
  }),
397
507
  );
398
508
  }
@@ -402,6 +512,8 @@ export function buildDecorations(
402
512
  className: getWorkflowBlockedRailClass(reason),
403
513
  attrs: {
404
514
  "data-workflow-blocked-code": reason.code,
515
+ ...(reason.fragmentId ? { "data-workflow-fragment-id": reason.fragmentId } : {}),
516
+ "data-workflow-zone": "locked",
405
517
  },
406
518
  }, railRangeCache);
407
519
  }
@@ -489,6 +489,7 @@ export const editorSchema = new Schema({
489
489
  label: { default: "Locked" },
490
490
  detail: { default: "" },
491
491
  presentation: { default: "inline-chip" },
492
+ displayText: { default: null },
492
493
  },
493
494
  toDOM(node) {
494
495
  const presentation = node.attrs.presentation as string;
@@ -505,6 +506,34 @@ export const editorSchema = new Schema({
505
506
  },
506
507
  ];
507
508
  }
509
+ if (presentation === "text-box") {
510
+ return [
511
+ "span",
512
+ {
513
+ class: "mx-0.5 inline-flex max-w-full whitespace-pre-wrap rounded border border-slate-300 bg-slate-50 px-2 py-1 align-top text-sm leading-snug text-slate-700 shadow-sm",
514
+ "data-node-type": "opaque_inline",
515
+ "data-inline-presentation": "text-box",
516
+ contenteditable: "false",
517
+ title: node.attrs.detail as string,
518
+ "aria-label": node.attrs.label as string,
519
+ },
520
+ (node.attrs.displayText as string | null) ?? (node.attrs.label as string),
521
+ ];
522
+ }
523
+ if (presentation === "checkbox") {
524
+ return [
525
+ "span",
526
+ {
527
+ class: "mx-0.5 inline-flex h-5 min-w-[1.25rem] items-center justify-center rounded border border-slate-300 bg-white px-1 align-text-bottom text-sm leading-none text-slate-700",
528
+ "data-node-type": "opaque_inline",
529
+ "data-inline-presentation": "checkbox",
530
+ contenteditable: "false",
531
+ title: node.attrs.detail as string,
532
+ "aria-label": node.attrs.label as string,
533
+ },
534
+ (node.attrs.displayText as string | null) ?? "☐",
535
+ ];
536
+ }
508
537
  return [
509
538
  "span",
510
539
  {
@@ -508,6 +508,17 @@ function buildOpaqueInlineOrComplexAtom(
508
508
  const label = segment.label;
509
509
  const detail = segment.detail;
510
510
 
511
+ if (segment.presentation === "text-box" || segment.presentation === "checkbox") {
512
+ return editorSchema.nodes.opaque_inline.create({
513
+ fragmentId: segment.fragmentId,
514
+ warningId: segment.warningId,
515
+ label,
516
+ detail,
517
+ presentation: segment.presentation,
518
+ displayText: segment.displayText ?? null,
519
+ });
520
+ }
521
+
511
522
  if (showUnsupportedObjectPreviews && label === "Embedded chart") {
512
523
  return editorSchema.nodes.chart_atom.create({ detail });
513
524
  }
@@ -548,6 +559,7 @@ function buildOpaqueInlineOrComplexAtom(
548
559
  label,
549
560
  detail,
550
561
  presentation: segment.presentation ?? "inline-chip",
562
+ displayText: segment.displayText ?? null,
551
563
  });
552
564
  }
553
565
 
@@ -43,6 +43,7 @@ export function createSurfaceDecorationKey(input: {
43
43
  workflowScopeSignature?: string;
44
44
  workflowCandidateSignature?: string;
45
45
  workflowBlockedSignature?: string;
46
+ workflowLockedZoneSignature?: string;
46
47
  workflowMetadataSignature?: string;
47
48
  activeWorkflowWorkItemId?: string | null;
48
49
  activeWorkflowScopeIds?: readonly string[];
@@ -56,6 +57,7 @@ export function createSurfaceDecorationKey(input: {
56
57
  workflowScopeSignature: input.workflowScopeSignature ?? null,
57
58
  workflowCandidateSignature: input.workflowCandidateSignature ?? null,
58
59
  workflowBlockedSignature: input.workflowBlockedSignature ?? null,
60
+ workflowLockedZoneSignature: input.workflowLockedZoneSignature ?? null,
59
61
  workflowMetadataSignature: input.workflowMetadataSignature ?? null,
60
62
  activeWorkflowWorkItemId: input.activeWorkflowWorkItemId ?? null,
61
63
  activeWorkflowScopeIds: input.activeWorkflowScopeIds ?? [],
@@ -106,6 +106,26 @@ export function TwInlineToken(props: TwInlineTokenProps) {
106
106
  );
107
107
  }
108
108
 
109
+ if (segment.presentation === "checkbox") {
110
+ return (
111
+ <button
112
+ type="button"
113
+ tabIndex={-1}
114
+ onMouseDown={(e) => {
115
+ e.preventDefault();
116
+ props.onSelectionChange?.(createSelectionSnapshot(segment.from, segment.to));
117
+ }}
118
+ className={`inline-flex h-5 min-w-[1.25rem] items-center justify-center rounded border border-slate-300 bg-white px-1 text-sm leading-none text-slate-700 ${commentClass} ${selected ? "ring-1 ring-accent/30" : ""} ${focusRingClass}`}
119
+ title={segment.detail}
120
+ data-inline-presentation="checkbox"
121
+ >
122
+ {renderTwCaret(selection, segment.from)}
123
+ {segment.displayText ?? "☐"}
124
+ {renderTwCaret(selection, segment.to)}
125
+ </button>
126
+ );
127
+ }
128
+
109
129
  return (
110
130
  <button
111
131
  type="button"
@@ -18,6 +18,7 @@ import type {
18
18
  SelectionSnapshot,
19
19
  WorkflowBlockedCommandReason,
20
20
  WorkflowCandidateRange,
21
+ WorkflowLockedZone,
21
22
  WorkflowMetadataMarkup,
22
23
  WorkflowScope,
23
24
  } from "../../api/public-types";
@@ -79,6 +80,7 @@ export interface TwProseMirrorSurfaceProps {
79
80
  activeRevisionId?: string;
80
81
  activeSelectionToolKind?: ActiveSelectionToolModel["kind"] | null;
81
82
  showTrackedChanges?: boolean;
83
+ suggestionsEnabled?: boolean;
82
84
  /** When true, the surface renders inside the page workspace (vs canvas). */
83
85
  isPageWorkspace?: boolean;
84
86
  onFocus: FocusEventHandler<HTMLDivElement>;
@@ -101,6 +103,7 @@ export interface TwProseMirrorSurfaceProps {
101
103
  workflowScopes?: readonly WorkflowScope[];
102
104
  workflowCandidates?: readonly WorkflowCandidateRange[];
103
105
  workflowBlockedReasons?: readonly WorkflowBlockedCommandReason[];
106
+ workflowLockedZones?: readonly WorkflowLockedZone[];
104
107
  activeWorkflowWorkItemId?: string | null;
105
108
  activeWorkflowScopeIds?: readonly string[];
106
109
  workflowMetadata?: readonly WorkflowMetadataMarkup[];
@@ -194,6 +197,7 @@ export const TwProseMirrorSurface = forwardRef<
194
197
  [snapshot.comments],
195
198
  );
196
199
  const showTrackedChanges = props.showTrackedChanges !== false;
200
+ const suggestionsEnabled = props.suggestionsEnabled ?? false;
197
201
  // Always create the revision model — needed for deletion hiding in clean mode
198
202
  // even when the tracked changes display toggle is off.
199
203
  const revisionModel = useMemo(
@@ -221,6 +225,7 @@ export const TwProseMirrorSurface = forwardRef<
221
225
  workflowScopeSignature: createWorkflowScopeSignature(props.workflowScopes),
222
226
  workflowCandidateSignature: createWorkflowCandidateSignature(props.workflowCandidates),
223
227
  workflowBlockedSignature: createWorkflowBlockedSignature(props.workflowBlockedReasons),
228
+ workflowLockedZoneSignature: createWorkflowLockedZoneSignature(props.workflowLockedZones),
224
229
  workflowMetadataSignature: createWorkflowMetadataSignature(props.workflowMetadata),
225
230
  activeWorkflowWorkItemId: props.activeWorkflowWorkItemId ?? null,
226
231
  activeWorkflowScopeIds: props.activeWorkflowScopeIds ?? [],
@@ -231,6 +236,7 @@ export const TwProseMirrorSurface = forwardRef<
231
236
  props.activeRevisionId,
232
237
  props.workflowCandidates,
233
238
  props.workflowBlockedReasons,
239
+ props.workflowLockedZones,
234
240
  props.workflowMetadata,
235
241
  props.activeWorkflowWorkItemId,
236
242
  props.activeWorkflowScopeIds,
@@ -276,10 +282,12 @@ export const TwProseMirrorSurface = forwardRef<
276
282
  revisionModel,
277
283
  markupDisplay,
278
284
  showTrackedChanges,
285
+ suggestionsEnabled,
279
286
  props.workflowScopes,
280
287
  snapshot.activeStory,
281
288
  props.workflowCandidates,
282
289
  props.workflowBlockedReasons,
290
+ props.workflowLockedZones,
283
291
  props.activeWorkflowWorkItemId,
284
292
  props.activeWorkflowScopeIds,
285
293
  props.workflowMetadata,
@@ -300,11 +308,13 @@ export const TwProseMirrorSurface = forwardRef<
300
308
  props.activeWorkflowScopeIds,
301
309
  props.activeWorkflowWorkItemId,
302
310
  props.workflowBlockedReasons,
311
+ props.workflowLockedZones,
303
312
  props.workflowMetadata,
304
313
  props.workflowCandidates,
305
314
  props.workflowScopes,
306
315
  revisionModel,
307
316
  showTrackedChanges,
317
+ suggestionsEnabled,
308
318
  ],
309
319
  );
310
320
 
@@ -331,10 +341,12 @@ export const TwProseMirrorSurface = forwardRef<
331
341
  revisionModel,
332
342
  markupDisplay,
333
343
  showTrackedChanges,
344
+ suggestionsEnabled,
334
345
  props.workflowScopes,
335
346
  snapshot.activeStory,
336
347
  props.workflowCandidates,
337
348
  props.workflowBlockedReasons,
349
+ props.workflowLockedZones,
338
350
  props.activeWorkflowWorkItemId,
339
351
  props.activeWorkflowScopeIds,
340
352
  props.workflowMetadata,
@@ -898,6 +910,24 @@ function createWorkflowBlockedSignature(
898
910
  ).join("|");
899
911
  }
900
912
 
913
+ function createWorkflowLockedZoneSignature(
914
+ lockedZones: readonly WorkflowLockedZone[] | undefined,
915
+ ): string {
916
+ if (!lockedZones || lockedZones.length === 0) {
917
+ return "";
918
+ }
919
+ return lockedZones.map((zone) =>
920
+ [
921
+ zone.fragmentId,
922
+ zone.code,
923
+ zone.label,
924
+ zone.detail,
925
+ serializeAnchorSignature(zone.anchor),
926
+ serializeStoryTargetSignature(zone.storyTarget),
927
+ ].join(":")
928
+ ).join("|");
929
+ }
930
+
901
931
  function createWorkflowMetadataSignature(
902
932
  metadata: readonly WorkflowMetadataMarkup[] | undefined,
903
933
  ): string {
@@ -922,6 +952,7 @@ function serializeAnchorSignature(
922
952
  | WorkflowScope["anchor"]
923
953
  | WorkflowCandidateRange["anchor"]
924
954
  | WorkflowBlockedCommandReason["anchor"]
955
+ | WorkflowLockedZone["anchor"]
925
956
  | WorkflowMetadataMarkup["anchor"]
926
957
  | undefined,
927
958
  ): string {
@@ -326,6 +326,10 @@
326
326
  text-underline-offset: 0.18em;
327
327
  }
328
328
 
329
+ .prosemirror-surface .ProseMirror .wre-workflow-inline-locked-zone {
330
+ border-radius: 0.35rem;
331
+ }
332
+
329
333
  .prosemirror-surface .ProseMirror .wre-workflow-rail {
330
334
  position: relative;
331
335
  padding-left: 0.875rem;
@@ -421,6 +425,10 @@
421
425
  );
422
426
  }
423
427
 
428
+ .prosemirror-surface .ProseMirror .wre-workflow-rail-locked-zone {
429
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--wre-workflow-rail-color, var(--color-danger)) 10%, transparent);
430
+ }
431
+
424
432
  .prosemirror-surface .ProseMirror .wre-workflow-rail-selection-zone {
425
433
  background: transparent;
426
434
  }