@beyondwork/docx-react-component 1.0.24-rc → 1.0.26-rc

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@beyondwork/docx-react-component",
3
3
  "publisher": "beyondwork",
4
- "version": "1.0.24rc",
4
+ "version": "1.0.26rc",
5
5
  "description": "Embeddable React Word (docx) editor with review, comments, tracked changes, and round-trip OOXML fidelity.",
6
6
  "type": "module",
7
7
  "sideEffects": [
@@ -104,7 +104,6 @@ export function rangeStaysWithinSingleParagraph(
104
104
  }
105
105
 
106
106
  export function canCreateDocxCommentAnchor(
107
- content: unknown,
108
107
  anchor: ReviewAnchor,
109
108
  ): boolean {
110
109
  if (anchor.kind !== "range") {
@@ -112,11 +111,7 @@ export function canCreateDocxCommentAnchor(
112
111
  }
113
112
 
114
113
  const normalized = normalizeRange(anchor.range);
115
- if (normalized.from === normalized.to) {
116
- return false;
117
- }
118
-
119
- return rangeStaysWithinSingleParagraph(content, normalized);
114
+ return normalized.from !== normalized.to;
120
115
  }
121
116
 
122
117
  function readSurfaceBlocks(
@@ -261,19 +261,25 @@ export function serializeCommentAnchorsIntoDocumentXml(
261
261
  continue;
262
262
  }
263
263
 
264
- const paragraph = paragraphs.find(
264
+ const startParagraph = paragraphs.find(
265
265
  (candidate) =>
266
266
  anchor.range.from >= candidate.start &&
267
+ anchor.range.from <= candidate.end,
268
+ );
269
+
270
+ const endParagraph = paragraphs.find(
271
+ (candidate) =>
272
+ anchor.range.to >= candidate.start &&
267
273
  anchor.range.to <= candidate.end,
268
274
  );
269
275
 
270
- if (!paragraph) {
276
+ if (!startParagraph || !endParagraph) {
271
277
  skippedCommentIds.push(thread.commentId);
272
278
  continue;
273
279
  }
274
280
 
275
- const startIndex = paragraph.boundaries.get(anchor.range.from);
276
- const endIndex = paragraph.boundaries.get(anchor.range.to);
281
+ const startIndex = startParagraph.boundaries.get(anchor.range.from);
282
+ const endIndex = endParagraph.boundaries.get(anchor.range.to);
277
283
 
278
284
  if (startIndex === undefined || endIndex === undefined) {
279
285
  skippedCommentIds.push(thread.commentId);
@@ -1160,9 +1160,9 @@ export function createDocumentRuntime(
1160
1160
  const selection = params.anchor
1161
1161
  ? createSelectionFromPublicAnchor(params.anchor)
1162
1162
  : state.selection;
1163
- if (!canCreateDocxCommentAnchor(state.document.content, anchor)) {
1163
+ if (!canCreateDocxCommentAnchor(anchor)) {
1164
1164
  const message =
1165
- "DOCX comments must use a non-empty range that stays within a single paragraph.";
1165
+ "DOCX comments must use a non-empty range.";
1166
1166
  emitError({
1167
1167
  errorId: createSessionId("comment-anchor", clock()),
1168
1168
  code: "validation_failed",
@@ -110,7 +110,7 @@ export function deriveCapabilities(
110
110
  activeStory.kind === "main" &&
111
111
  !snapshot.selection.isCollapsed &&
112
112
  Boolean(snapshot.surface) &&
113
- canCreateDocxCommentAnchor(snapshot.surface, toRuntimeAnchor(snapshot.selection.activeRange));
113
+ canCreateDocxCommentAnchor(toRuntimeAnchor(snapshot.selection.activeRange));
114
114
  const canExport = isReady && !exportBlocked && !hasFatalError;
115
115
 
116
116
  // Revision capabilities
@@ -21,7 +21,22 @@ type RailDecorationSpec = {
21
21
  attrs: Record<string, string>;
22
22
  };
23
23
 
24
- function getWorkflowInlineClass(scope: WorkflowScope, isActiveWorkItem: boolean): string {
24
+ function isSelectionZoneScope(scope: WorkflowScope): boolean {
25
+ return (
26
+ scope.scopeId.startsWith("scope-lab-") ||
27
+ scope.scopeId.startsWith("metadata-lab-") ||
28
+ scope.workItemId?.startsWith("scope-lab-") === true ||
29
+ scope.workItemId?.startsWith("metadata-lab-") === true ||
30
+ scope.label?.toLowerCase().includes("scope lab") === true ||
31
+ scope.label?.toLowerCase().includes("metadata lab") === true
32
+ );
33
+ }
34
+
35
+ function getWorkflowInlineClass(
36
+ scope: WorkflowScope,
37
+ isActiveWorkItem: boolean,
38
+ isSelectionZone: boolean,
39
+ ): string {
25
40
  const base =
26
41
  scope.mode === "edit"
27
42
  ? "wre-workflow-inline wre-workflow-inline-edit"
@@ -30,10 +45,15 @@ function getWorkflowInlineClass(scope: WorkflowScope, isActiveWorkItem: boolean)
30
45
  : scope.mode === "comment"
31
46
  ? "wre-workflow-inline wre-workflow-inline-comment"
32
47
  : "wre-workflow-inline wre-workflow-inline-view";
33
- return isActiveWorkItem ? `${base} wre-workflow-inline-active` : base;
48
+ const withZone = isSelectionZone ? `${base} wre-workflow-inline-zone` : base;
49
+ return isActiveWorkItem ? `${withZone} wre-workflow-inline-active` : withZone;
34
50
  }
35
51
 
36
- function getWorkflowRailClass(scope: WorkflowScope, isActiveWorkItem: boolean): string {
52
+ function getWorkflowRailClass(
53
+ scope: WorkflowScope,
54
+ isActiveWorkItem: boolean,
55
+ isSelectionZone: boolean,
56
+ ): string {
37
57
  const base =
38
58
  scope.mode === "edit"
39
59
  ? "wre-workflow-rail wre-workflow-rail-edit"
@@ -42,7 +62,8 @@ function getWorkflowRailClass(scope: WorkflowScope, isActiveWorkItem: boolean):
42
62
  : scope.mode === "comment"
43
63
  ? "wre-workflow-rail wre-workflow-rail-comment"
44
64
  : "wre-workflow-rail wre-workflow-rail-view";
45
- return isActiveWorkItem ? `${base} wre-workflow-rail-active` : base;
65
+ const withZone = isSelectionZone ? `${base} wre-workflow-rail-selection-zone` : base;
66
+ return isActiveWorkItem ? `${withZone} wre-workflow-rail-active` : withZone;
46
67
  }
47
68
 
48
69
  function getWorkflowCandidateInlineClass(): string {
@@ -311,6 +332,7 @@ export function buildDecorations(
311
332
  if (!storyTargetsEqual(scopeStoryTarget, activeStory)) continue;
312
333
  const pmRange = buildAnchorPmRange(scope.anchor, positionMap);
313
334
  if (!pmRange) continue;
335
+ const isSelectionZone = isSelectionZoneScope(scope);
314
336
  const isActiveWorkItem =
315
337
  Boolean(activeWorkflowWorkItemId) &&
316
338
  (
@@ -321,21 +343,23 @@ export function buildDecorations(
321
343
  if (pmRange.allowInline && pmRange.from < pmRange.to) {
322
344
  decorations.push(
323
345
  Decoration.inline(pmRange.from, pmRange.to, {
324
- class: getWorkflowInlineClass(scope, isActiveWorkItem),
346
+ class: getWorkflowInlineClass(scope, isActiveWorkItem, isSelectionZone),
325
347
  "data-workflow-scope-id": scope.scopeId,
326
348
  "data-workflow-scope-mode": scope.mode,
327
349
  "data-workflow-active": isActiveWorkItem ? "true" : "false",
350
+ ...(isSelectionZone ? { "data-workflow-zone": "selection" } : {}),
328
351
  }),
329
352
  );
330
353
  }
331
354
 
332
355
  pushRailDecorations(decorations, doc, pmRange.from, pmRange.to, {
333
356
  railKind: "scope",
334
- className: getWorkflowRailClass(scope, isActiveWorkItem),
357
+ className: getWorkflowRailClass(scope, isActiveWorkItem, isSelectionZone),
335
358
  attrs: {
336
359
  "data-workflow-scope-id": scope.scopeId,
337
360
  "data-workflow-scope-mode": scope.mode,
338
361
  "data-workflow-active": isActiveWorkItem ? "true" : "false",
362
+ ...(isSelectionZone ? { "data-workflow-zone": "selection" } : {}),
339
363
  },
340
364
  }, railRangeCache);
341
365
  }
@@ -261,6 +261,8 @@
261
261
  .prosemirror-surface .ProseMirror .wre-workflow-inline {
262
262
  border-radius: 0.25rem;
263
263
  box-shadow: inset 0 0 0 1px transparent;
264
+ -webkit-box-decoration-break: clone;
265
+ box-decoration-break: clone;
264
266
  }
265
267
 
266
268
  .prosemirror-surface .ProseMirror .wre-workflow-inline-edit {
@@ -333,6 +335,13 @@
333
335
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--wre-workflow-rail-color, var(--color-border-strong)) 28%, transparent);
334
336
  }
335
337
 
338
+ .prosemirror-surface .ProseMirror .wre-workflow-inline-zone {
339
+ border-radius: 0.35rem;
340
+ box-shadow:
341
+ inset 0 0 0 1px color-mix(in srgb, var(--wre-workflow-rail-color, var(--color-accent)) 18%, transparent),
342
+ inset 0 0 0 999px color-mix(in srgb, var(--wre-workflow-rail-color, var(--color-accent)) 8%, transparent);
343
+ }
344
+
336
345
  .prosemirror-surface .ProseMirror .wre-workflow-rail-edit {
337
346
  --wre-workflow-rail-color: var(--color-accent);
338
347
  background: color-mix(in srgb, var(--color-accent) 7%, transparent);
@@ -394,6 +403,15 @@
394
403
  );
395
404
  }
396
405
 
406
+ .prosemirror-surface .ProseMirror .wre-workflow-rail-selection-zone {
407
+ background: transparent;
408
+ }
409
+
410
+ .prosemirror-surface .ProseMirror .wre-workflow-rail-selection-zone::before {
411
+ width: 0.25rem;
412
+ opacity: 0.95;
413
+ }
414
+
397
415
  .prosemirror-surface:focus-visible {
398
416
  outline: none;
399
417
  }