@beyondwork/docx-react-component 1.0.28 → 1.0.30

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.
Files changed (92) hide show
  1. package/package.json +26 -37
  2. package/src/api/public-types.ts +531 -0
  3. package/src/api/session-state.ts +2 -0
  4. package/src/core/commands/index.ts +201 -79
  5. package/src/core/commands/table-structure-commands.ts +138 -5
  6. package/src/core/state/text-transaction.ts +370 -3
  7. package/src/index.ts +41 -0
  8. package/src/io/docx-session.ts +318 -25
  9. package/src/io/export/serialize-footnotes.ts +41 -46
  10. package/src/io/export/serialize-headers-footers.ts +36 -40
  11. package/src/io/export/serialize-main-document.ts +55 -89
  12. package/src/io/export/serialize-numbering.ts +104 -4
  13. package/src/io/export/serialize-runtime-revisions.ts +196 -2
  14. package/src/io/export/split-story-blocks-for-runtime-revisions.ts +252 -0
  15. package/src/io/export/table-properties-xml.ts +318 -0
  16. package/src/io/normalize/normalize-text.ts +34 -3
  17. package/src/io/ooxml/parse-comments.ts +6 -0
  18. package/src/io/ooxml/parse-footnotes.ts +69 -13
  19. package/src/io/ooxml/parse-headers-footers.ts +54 -11
  20. package/src/io/ooxml/parse-main-document.ts +112 -42
  21. package/src/io/ooxml/parse-numbering.ts +341 -26
  22. package/src/io/ooxml/parse-revisions.ts +118 -4
  23. package/src/io/ooxml/parse-styles.ts +176 -0
  24. package/src/io/ooxml/parse-tables.ts +34 -25
  25. package/src/io/ooxml/revision-boundaries.ts +127 -3
  26. package/src/io/ooxml/workflow-payload.ts +544 -0
  27. package/src/model/canonical-document.ts +91 -1
  28. package/src/model/snapshot.ts +112 -1
  29. package/src/preservation/store.ts +73 -3
  30. package/src/review/store/comment-store.ts +19 -1
  31. package/src/review/store/revision-actions.ts +29 -0
  32. package/src/review/store/revision-store.ts +12 -1
  33. package/src/review/store/revision-types.ts +11 -0
  34. package/src/runtime/context-analytics.ts +824 -0
  35. package/src/runtime/document-locations.ts +521 -0
  36. package/src/runtime/document-navigation.ts +14 -1
  37. package/src/runtime/document-outline.ts +440 -0
  38. package/src/runtime/document-runtime.ts +941 -45
  39. package/src/runtime/event-refresh-hints.ts +137 -0
  40. package/src/runtime/numbering-prefix.ts +67 -39
  41. package/src/runtime/page-layout-estimation.ts +100 -7
  42. package/src/runtime/resolved-numbering-geometry.ts +293 -0
  43. package/src/runtime/session-capabilities.ts +2 -2
  44. package/src/runtime/suggestions-snapshot.ts +137 -0
  45. package/src/runtime/surface-projection.ts +223 -27
  46. package/src/runtime/table-style-resolver.ts +409 -0
  47. package/src/runtime/view-state.ts +17 -1
  48. package/src/runtime/workflow-markup.ts +54 -14
  49. package/src/ui/WordReviewEditor.tsx +1269 -87
  50. package/src/ui/editor-command-bag.ts +7 -0
  51. package/src/ui/editor-runtime-boundary.ts +111 -10
  52. package/src/ui/editor-shell-view.tsx +17 -15
  53. package/src/ui/editor-surface-controller.tsx +5 -0
  54. package/src/ui/headless/selection-tool-context.ts +19 -0
  55. package/src/ui/headless/selection-tool-resolver.ts +752 -0
  56. package/src/ui/headless/selection-tool-types.ts +129 -0
  57. package/src/ui/headless/selection-toolbar-model.ts +10 -33
  58. package/src/ui/runtime-shortcut-dispatch.ts +365 -0
  59. package/src/ui-tailwind/chrome/chrome-preset-model.ts +107 -0
  60. package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +15 -0
  61. package/src/ui-tailwind/chrome/review-queue-bar.tsx +97 -0
  62. package/src/ui-tailwind/chrome/tw-context-analytics-summary.tsx +122 -0
  63. package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +1 -9
  64. package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +1 -5
  65. package/src/ui-tailwind/chrome/tw-page-ruler.tsx +8 -29
  66. package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +23 -0
  67. package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +35 -0
  68. package/src/ui-tailwind/chrome/tw-selection-tool-formatting.tsx +37 -0
  69. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +298 -0
  70. package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +116 -0
  71. package/src/ui-tailwind/chrome/tw-selection-tool-suggestion.tsx +29 -0
  72. package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +27 -0
  73. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +3 -3
  74. package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +3 -3
  75. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +86 -14
  76. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +57 -52
  77. package/src/ui-tailwind/editor-surface/pm-decorations.ts +36 -52
  78. package/src/ui-tailwind/editor-surface/pm-schema.ts +56 -5
  79. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +87 -24
  80. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +4 -0
  81. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +135 -32
  82. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +74 -7
  83. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +17 -17
  84. package/src/ui-tailwind/review/tw-review-rail.tsx +19 -17
  85. package/src/ui-tailwind/review/tw-revision-sidebar.tsx +10 -10
  86. package/src/ui-tailwind/status/tw-status-bar.tsx +10 -6
  87. package/src/ui-tailwind/theme/editor-theme.css +58 -40
  88. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +4 -4
  89. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +250 -181
  90. package/src/ui-tailwind/tw-review-workspace.tsx +323 -280
  91. package/src/validation/compatibility-engine.ts +246 -2
  92. package/src/validation/docx-comment-proof.ts +24 -11
@@ -0,0 +1,97 @@
1
+ import React from "react";
2
+
3
+ import { BookmarkPlus, ChevronLeft, ChevronRight, MessageSquareText, Rows3 } from "lucide-react";
4
+
5
+ import type { ReviewQueueSnapshot } from "../../api/public-types";
6
+ import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-editor-selection";
7
+
8
+ export interface TwReviewQueueBarProps {
9
+ queue: ReviewQueueSnapshot;
10
+ onPrevious?: () => void;
11
+ onNext?: () => void;
12
+ onMarkSection?: () => void;
13
+ }
14
+
15
+ const buttonClass =
16
+ "inline-flex h-8 items-center gap-1 rounded-lg border border-border/80 bg-canvas px-2.5 text-xs font-medium text-secondary transition-colors hover:bg-surface-raised focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-canvas disabled:cursor-not-allowed disabled:opacity-40";
17
+
18
+ export function TwReviewQueueBar(props: TwReviewQueueBarProps) {
19
+ const activeItem = props.queue.items[props.queue.activeIndex] ?? null;
20
+ const activeLabel =
21
+ activeItem?.label ??
22
+ (props.queue.totalCount > 0 ? "Review queue" : "No review items");
23
+ const activeKindLabel = activeItem ? formatReviewQueueItemKind(activeItem.kind) : null;
24
+
25
+ return (
26
+ <div className="border-b border-border/80 bg-surface/80 px-3 py-2.5 backdrop-blur-sm">
27
+ <div className="flex flex-wrap items-center gap-2">
28
+ <button
29
+ type="button"
30
+ aria-label="Previous review item"
31
+ className={buttonClass}
32
+ disabled={props.queue.totalCount === 0}
33
+ onMouseDown={preserveEditorSelectionMouseDown}
34
+ onClick={props.onPrevious}
35
+ >
36
+ <ChevronLeft className="h-3.5 w-3.5" />
37
+ Prev
38
+ </button>
39
+ <button
40
+ type="button"
41
+ aria-label="Next review item"
42
+ className={buttonClass}
43
+ disabled={props.queue.totalCount === 0}
44
+ onMouseDown={preserveEditorSelectionMouseDown}
45
+ onClick={props.onNext}
46
+ >
47
+ Next
48
+ <ChevronRight className="h-3.5 w-3.5" />
49
+ </button>
50
+ {props.onMarkSection ? (
51
+ <button
52
+ type="button"
53
+ aria-label="Mark section for review"
54
+ className={buttonClass}
55
+ onMouseDown={preserveEditorSelectionMouseDown}
56
+ onClick={props.onMarkSection}
57
+ >
58
+ <BookmarkPlus className="h-3.5 w-3.5" />
59
+ Mark section
60
+ </button>
61
+ ) : null}
62
+ <div className="ml-auto flex flex-wrap items-center gap-2 text-xs text-secondary">
63
+ <span className="inline-flex items-center gap-1 rounded-full bg-canvas px-2.5 py-1 font-medium text-primary">
64
+ <MessageSquareText className="h-3.5 w-3.5 text-comment" />
65
+ {props.queue.openCount} open
66
+ </span>
67
+ <span className="inline-flex items-center gap-1 rounded-full bg-canvas px-2.5 py-1 font-medium text-primary">
68
+ <Rows3 className="h-3.5 w-3.5 text-accent" />
69
+ {props.queue.totalCount} total
70
+ </span>
71
+ </div>
72
+ </div>
73
+ <div className="mt-2 flex items-center gap-2 min-w-0">
74
+ {activeKindLabel ? (
75
+ <span
76
+ className="inline-flex shrink-0 rounded-full bg-canvas px-2 py-0.5 text-[11px] font-medium text-secondary"
77
+ data-testid="review-queue-item-kind"
78
+ >
79
+ {activeKindLabel}
80
+ </span>
81
+ ) : null}
82
+ <div className="min-w-0 truncate text-sm font-medium text-primary">{activeLabel}</div>
83
+ </div>
84
+ </div>
85
+ );
86
+ }
87
+
88
+ function formatReviewQueueItemKind(kind: ReviewQueueSnapshot["items"][number]["kind"]): string {
89
+ switch (kind) {
90
+ case "comment":
91
+ return "Comment";
92
+ case "change":
93
+ return "Redline";
94
+ case "section_mark":
95
+ return "Tag";
96
+ }
97
+ }
@@ -0,0 +1,122 @@
1
+ import React from "react";
2
+
3
+ import type { RuntimeContextAnalyticsSnapshot } from "../../api/public-types";
4
+
5
+ const TONE_CLASS: Record<NonNullable<RuntimeContextAnalyticsSnapshot["badges"][number]["tone"]>, string> = {
6
+ neutral: "bg-surface text-secondary",
7
+ accent: "bg-accent-soft text-accent",
8
+ warning: "bg-warning-soft text-warning",
9
+ danger: "bg-danger-soft text-danger",
10
+ success: "bg-success-soft text-success",
11
+ };
12
+
13
+ export interface TwContextAnalyticsSummaryProps {
14
+ snapshot?: RuntimeContextAnalyticsSnapshot | null;
15
+ compact?: boolean;
16
+ testId?: string;
17
+ className?: string;
18
+ }
19
+
20
+ export function TwContextAnalyticsSummary(props: TwContextAnalyticsSummaryProps) {
21
+ if (!props.snapshot) {
22
+ return null;
23
+ }
24
+
25
+ const badges = props.snapshot.badges.slice(0, props.compact ? 3 : 4);
26
+ const primaryAction = props.snapshot.nextActions[0];
27
+ if (badges.length === 0 && !primaryAction) {
28
+ return null;
29
+ }
30
+
31
+ return (
32
+ <div
33
+ data-testid={props.testId}
34
+ className={[
35
+ "rounded-lg bg-surface/92 p-2.5 shadow-[0_1px_0_var(--color-shadow)] ring-1 ring-border/40",
36
+ props.className,
37
+ ]
38
+ .filter(Boolean)
39
+ .join(" ")}
40
+ >
41
+ {badges.length > 0 ? (
42
+ <div className="flex flex-wrap gap-1.5">
43
+ {badges.map((badge) => (
44
+ <span
45
+ key={badge.key}
46
+ className={[
47
+ "inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium",
48
+ TONE_CLASS[badge.tone ?? "neutral"],
49
+ ].join(" ")}
50
+ >
51
+ {formatBadgeLabel(badge)}
52
+ </span>
53
+ ))}
54
+ </div>
55
+ ) : null}
56
+ {primaryAction ? (
57
+ <p className="mt-1 text-[11px] text-secondary">
58
+ {primaryAction.label}
59
+ </p>
60
+ ) : null}
61
+ </div>
62
+ );
63
+ }
64
+
65
+ export function formatContextAnalyticsInlineSummary(
66
+ snapshot?: RuntimeContextAnalyticsSnapshot | null,
67
+ ): string | null {
68
+ if (!snapshot) {
69
+ return null;
70
+ }
71
+
72
+ const parts: string[] = [];
73
+ if (typeof snapshot.counts.openWorkItems === "number" && snapshot.counts.openWorkItems > 0) {
74
+ parts.push(`${snapshot.counts.openWorkItems} open ${pluralize(snapshot.counts.openWorkItems, "work item")}`);
75
+ }
76
+ if (
77
+ typeof snapshot.counts.unresolvedComments === "number" &&
78
+ snapshot.counts.unresolvedComments > 0
79
+ ) {
80
+ parts.push(
81
+ `${snapshot.counts.unresolvedComments} unresolved ${pluralize(snapshot.counts.unresolvedComments, "comment")}`,
82
+ );
83
+ }
84
+ if (
85
+ typeof snapshot.counts.pendingRevisions === "number" &&
86
+ snapshot.counts.pendingRevisions > 0
87
+ ) {
88
+ parts.push(
89
+ `${snapshot.counts.pendingRevisions} pending ${pluralize(snapshot.counts.pendingRevisions, "change")}`,
90
+ );
91
+ }
92
+ if (
93
+ parts.length === 0 &&
94
+ typeof snapshot.counts.blockedCommands === "number" &&
95
+ snapshot.counts.blockedCommands > 0
96
+ ) {
97
+ parts.push(
98
+ `${snapshot.counts.blockedCommands} blocked ${pluralize(snapshot.counts.blockedCommands, "action")}`,
99
+ );
100
+ }
101
+ if (parts.length === 0) {
102
+ if (snapshot.state.exportReadiness === "blocked") {
103
+ parts.push("Export blocked");
104
+ } else if (snapshot.state.exportReadiness === "warning") {
105
+ parts.push("Export warnings");
106
+ }
107
+ }
108
+ if (parts.length === 0) {
109
+ return snapshot.badges[0] ? formatBadgeLabel(snapshot.badges[0]) : null;
110
+ }
111
+ return parts.slice(0, 3).join(" · ");
112
+ }
113
+
114
+ function formatBadgeLabel(
115
+ badge: RuntimeContextAnalyticsSnapshot["badges"][number],
116
+ ): string {
117
+ return badge.value === undefined ? badge.label : `${badge.label}: ${badge.value}`;
118
+ }
119
+
120
+ function pluralize(count: number, singular: string): string {
121
+ return count === 1 ? singular : `${singular}s`;
122
+ }
@@ -1,15 +1,7 @@
1
1
  import React from "react";
2
2
 
3
3
  import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-editor-selection";
4
-
5
- export interface ActiveImageContext {
6
- mediaId: string;
7
- display: "inline" | "floating";
8
- widthEmu?: number;
9
- heightEmu?: number;
10
- horizontalOffsetEmu?: number;
11
- verticalOffsetEmu?: number;
12
- }
4
+ import type { ActiveImageContext } from "../../ui/headless/selection-tool-types";
13
5
 
14
6
  export interface TwImageContextToolbarProps {
15
7
  activeImage: ActiveImageContext;
@@ -1,9 +1,5 @@
1
1
  import React from "react";
2
-
3
- export interface ActiveObjectContext {
4
- kind: "textbox" | "shape";
5
- display: "inline" | "floating";
6
- }
2
+ import type { ActiveObjectContext } from "../../ui/headless/selection-tool-types";
7
3
 
8
4
  export interface TwObjectContextToolbarProps {
9
5
  activeObject: ActiveObjectContext;
@@ -10,6 +10,8 @@ interface ActiveParagraphLayout {
10
10
  rightIndent: number;
11
11
  firstLineOffset: number;
12
12
  tabStops: Array<{ pos: number; val?: string; leader?: string }>;
13
+ indentationReadOnly?: boolean;
14
+ tabStopsReadOnly?: boolean;
13
15
  }
14
16
 
15
17
  export interface TwPageRulerProps {
@@ -53,6 +55,8 @@ export function TwPageRuler(props: TwPageRulerProps) {
53
55
  const activeRegion = props.viewState.activePageRegion?.region ?? "body";
54
56
  const isBodyParagraphContext =
55
57
  activeRegion === "body" && Boolean(props.paragraphLayout);
58
+ const indentationReadOnly = props.readOnly || Boolean(props.paragraphLayout?.indentationReadOnly);
59
+ const tabStopsReadOnly = props.readOnly || Boolean(props.paragraphLayout?.tabStopsReadOnly);
56
60
  const availableHeader = props.pageLayout.headerVariants[0];
57
61
  const availableFooter = props.pageLayout.footerVariants[0];
58
62
 
@@ -68,7 +72,7 @@ export function TwPageRuler(props: TwPageRulerProps) {
68
72
  }, []);
69
73
 
70
74
  function beginDrag(kind: DragKind, clientX: number): void {
71
- if (!isBodyParagraphContext || !props.paragraphLayout || props.readOnly) {
75
+ if (!isBodyParagraphContext || !props.paragraphLayout || indentationReadOnly) {
72
76
  return;
73
77
  }
74
78
 
@@ -211,31 +215,6 @@ export function TwPageRuler(props: TwPageRulerProps) {
211
215
  Footer
212
216
  </button>
213
217
  ) : null}
214
- {props.viewState.activeListContext ? (
215
- <>
216
- <div className="h-4 w-px bg-border" />
217
- <button
218
- type="button"
219
- aria-label="Continue numbering"
220
- title="Continue numbering from previous list"
221
- disabled={props.readOnly}
222
- onClick={props.onContinueNumbering}
223
- className={controlButtonClass}
224
- >
225
- Continue
226
- </button>
227
- <button
228
- type="button"
229
- aria-label="Restart numbering"
230
- title="Restart numbering at 1"
231
- disabled={props.readOnly}
232
- onClick={props.onRestartNumbering}
233
- className={controlButtonClass}
234
- >
235
- Restart
236
- </button>
237
- </>
238
- ) : null}
239
218
  </div>
240
219
 
241
220
  <div
@@ -252,7 +231,7 @@ export function TwPageRuler(props: TwPageRulerProps) {
252
231
  className="relative h-14 overflow-hidden rounded-xl border border-border bg-canvas"
253
232
  onClick={(event) => {
254
233
  if (
255
- props.readOnly ||
234
+ tabStopsReadOnly ||
256
235
  !isBodyParagraphContext ||
257
236
  !props.paragraphLayout ||
258
237
  !props.onSetTabStops
@@ -287,7 +266,7 @@ export function TwPageRuler(props: TwPageRulerProps) {
287
266
  data-handle="true"
288
267
  aria-label="Left indent handle"
289
268
  title={`Left indent: ${effectiveLayout?.leftIndent ?? 0} twips`}
290
- disabled={props.readOnly}
269
+ disabled={indentationReadOnly}
291
270
  className={`absolute top-8 h-4 w-4 -translate-x-1/2 rounded-[5px] border border-accent/40 bg-accent-soft shadow-sm transition-opacity ${
292
271
  handlesOverlap ? "opacity-80 z-10" : ""
293
272
  }`}
@@ -302,7 +281,7 @@ export function TwPageRuler(props: TwPageRulerProps) {
302
281
  data-handle="true"
303
282
  aria-label="First line indent handle"
304
283
  title={`First line offset: ${effectiveLayout?.firstLineOffset ?? 0} twips`}
305
- disabled={props.readOnly}
284
+ disabled={indentationReadOnly}
306
285
  className={`absolute top-1 h-4 w-4 -translate-x-1/2 rotate-45 rounded-[4px] border border-primary/30 bg-surface-raised shadow-sm transition-opacity ${
307
286
  handlesOverlap ? "opacity-80 z-20" : ""
308
287
  }`}
@@ -0,0 +1,23 @@
1
+ import React from "react";
2
+
3
+ import type { BlockedExplainerSelectionToolModel } from "../../ui/headless/selection-tool-types";
4
+
5
+ export interface TwSelectionToolBlockedProps {
6
+ model: BlockedExplainerSelectionToolModel;
7
+ }
8
+
9
+ export function TwSelectionToolBlocked(props: TwSelectionToolBlockedProps) {
10
+ return (
11
+ <div
12
+ data-testid="blocked-selection-tool"
13
+ className="max-w-[min(24rem,calc(100vw-2rem))] rounded-xl border border-border bg-canvas px-3 py-2 shadow-lg"
14
+ role="status"
15
+ aria-live="polite"
16
+ >
17
+ <div className="text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
18
+ Unavailable here
19
+ </div>
20
+ <div className="mt-1 text-sm text-primary">{props.model.disabledReason}</div>
21
+ </div>
22
+ );
23
+ }
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+
3
+ import type { CommentThreadSelectionToolModel } from "../../ui/headless/selection-tool-types";
4
+ import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-editor-selection";
5
+
6
+ export interface TwSelectionToolCommentProps {
7
+ model: CommentThreadSelectionToolModel;
8
+ onAddComment?: () => void;
9
+ }
10
+
11
+ export function TwSelectionToolComment(props: TwSelectionToolCommentProps) {
12
+ return (
13
+ <div
14
+ data-testid="comment-thread-selection-tool"
15
+ className="max-w-[min(24rem,calc(100vw-2rem))] rounded-xl border border-border bg-canvas px-3 py-2 shadow-lg"
16
+ >
17
+ <div className="text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
18
+ Comment
19
+ </div>
20
+ <div className="mt-1 truncate text-sm text-primary">
21
+ {props.model.previewText ?? "Comment thread"}
22
+ </div>
23
+ <button
24
+ type="button"
25
+ aria-label="Add comment from thread"
26
+ disabled={!props.model.canAddComment || !props.onAddComment}
27
+ onMouseDown={preserveEditorSelectionMouseDown}
28
+ onClick={props.onAddComment}
29
+ className="mt-2 inline-flex h-8 items-center rounded-lg border border-border px-2.5 text-xs font-medium text-secondary transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-40"
30
+ >
31
+ Comment
32
+ </button>
33
+ </div>
34
+ );
35
+ }
@@ -0,0 +1,37 @@
1
+ import React, { forwardRef } from "react";
2
+ import type { FocusEventHandler } from "react";
3
+
4
+ import type { FormattingInlineSelectionToolModel } from "../../ui/headless/selection-tool-types";
5
+ import { TwSelectionToolbar } from "./tw-selection-toolbar";
6
+
7
+ export interface TwSelectionToolFormattingProps {
8
+ model: FormattingInlineSelectionToolModel;
9
+ onFocusCapture?: FocusEventHandler<HTMLDivElement>;
10
+ onBlurCapture?: FocusEventHandler<HTMLDivElement>;
11
+ onToggleBold?: () => void;
12
+ onToggleItalic?: () => void;
13
+ onToggleUnderline?: () => void;
14
+ onSetTextColor?: (color: string) => void;
15
+ onSetHighlightColor?: (color: string | null) => void;
16
+ onAddComment?: () => void;
17
+ }
18
+
19
+ export const TwSelectionToolFormatting = forwardRef<HTMLDivElement, TwSelectionToolFormattingProps>(
20
+ function TwSelectionToolFormatting(props, ref) {
21
+ return (
22
+ <TwSelectionToolbar
23
+ ref={ref}
24
+ model={props.model}
25
+ disabledReason={props.model.disabledReason}
26
+ onFocusCapture={props.onFocusCapture}
27
+ onBlurCapture={props.onBlurCapture}
28
+ onToggleBold={props.onToggleBold}
29
+ onToggleItalic={props.onToggleItalic}
30
+ onToggleUnderline={props.onToggleUnderline}
31
+ onSetTextColor={props.onSetTextColor}
32
+ onSetHighlightColor={props.onSetHighlightColor}
33
+ onAddComment={props.onAddComment}
34
+ />
35
+ );
36
+ },
37
+ );