@beyondwork/docx-react-component 1.0.18 → 1.0.20

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 (105) hide show
  1. package/README.md +8 -2
  2. package/package.json +24 -34
  3. package/src/api/README.md +5 -1
  4. package/src/api/public-types.ts +710 -4
  5. package/src/api/session-state.ts +60 -0
  6. package/src/core/commands/formatting-commands.ts +2 -1
  7. package/src/core/commands/image-commands.ts +147 -0
  8. package/src/core/commands/index.ts +19 -3
  9. package/src/core/commands/list-commands.ts +231 -36
  10. package/src/core/commands/paragraph-layout-commands.ts +339 -0
  11. package/src/core/commands/section-layout-commands.ts +680 -0
  12. package/src/core/commands/style-commands.ts +262 -0
  13. package/src/core/search/search-text.ts +357 -0
  14. package/src/core/selection/mapping.ts +41 -0
  15. package/src/core/state/editor-state.ts +4 -1
  16. package/src/index.ts +51 -0
  17. package/src/io/docx-session.ts +623 -56
  18. package/src/io/export/serialize-comments.ts +104 -34
  19. package/src/io/export/serialize-footnotes.ts +198 -1
  20. package/src/io/export/serialize-headers-footers.ts +203 -10
  21. package/src/io/export/serialize-main-document.ts +285 -8
  22. package/src/io/export/serialize-numbering.ts +28 -7
  23. package/src/io/export/split-review-boundaries.ts +181 -19
  24. package/src/io/normalize/normalize-text.ts +144 -32
  25. package/src/io/ooxml/highlight-colors.ts +39 -0
  26. package/src/io/ooxml/numbering-sentinels.ts +44 -0
  27. package/src/io/ooxml/parse-comments.ts +85 -19
  28. package/src/io/ooxml/parse-fields.ts +396 -0
  29. package/src/io/ooxml/parse-footnotes.ts +452 -22
  30. package/src/io/ooxml/parse-headers-footers.ts +657 -29
  31. package/src/io/ooxml/parse-inline-media.ts +30 -0
  32. package/src/io/ooxml/parse-main-document.ts +807 -20
  33. package/src/io/ooxml/parse-numbering.ts +7 -0
  34. package/src/io/ooxml/parse-revisions.ts +317 -38
  35. package/src/io/ooxml/parse-settings.ts +184 -0
  36. package/src/io/ooxml/parse-shapes.ts +25 -0
  37. package/src/io/ooxml/parse-styles.ts +463 -0
  38. package/src/io/ooxml/parse-theme.ts +32 -0
  39. package/src/legal/bookmarks.ts +44 -0
  40. package/src/legal/cross-references.ts +59 -1
  41. package/src/model/canonical-document.ts +250 -4
  42. package/src/model/cds-1.0.0.ts +13 -0
  43. package/src/model/snapshot.ts +87 -2
  44. package/src/review/store/revision-store.ts +6 -0
  45. package/src/review/store/revision-types.ts +1 -0
  46. package/src/runtime/document-layout.ts +332 -0
  47. package/src/runtime/document-navigation.ts +603 -0
  48. package/src/runtime/document-runtime.ts +1754 -78
  49. package/src/runtime/document-search.ts +145 -0
  50. package/src/runtime/numbering-prefix.ts +47 -26
  51. package/src/runtime/page-layout-estimation.ts +212 -0
  52. package/src/runtime/read-only-diagnostics-runtime.ts +9 -0
  53. package/src/runtime/session-capabilities.ts +35 -3
  54. package/src/runtime/story-context.ts +164 -0
  55. package/src/runtime/story-targeting.ts +162 -0
  56. package/src/runtime/surface-projection.ts +324 -36
  57. package/src/runtime/table-schema.ts +89 -7
  58. package/src/runtime/view-state.ts +477 -0
  59. package/src/runtime/workflow-markup.ts +349 -0
  60. package/src/ui/WordReviewEditor.tsx +2469 -1344
  61. package/src/ui/browser-export.ts +52 -0
  62. package/src/ui/editor-command-bag.ts +120 -0
  63. package/src/ui/editor-runtime-boundary.ts +1422 -0
  64. package/src/ui/editor-shell-view.tsx +134 -0
  65. package/src/ui/editor-surface-controller.tsx +51 -0
  66. package/src/ui/headless/preserve-editor-selection.ts +5 -0
  67. package/src/ui/headless/revision-decoration-model.ts +4 -4
  68. package/src/ui/headless/selection-helpers.ts +20 -0
  69. package/src/ui/headless/selection-toolbar-model.ts +22 -0
  70. package/src/ui/headless/use-editor-keyboard.ts +6 -1
  71. package/src/ui/runtime-snapshot-selectors.ts +197 -0
  72. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +18 -2
  73. package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +129 -0
  74. package/src/ui-tailwind/chrome/tw-layout-panel.tsx +114 -0
  75. package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +34 -0
  76. package/src/ui-tailwind/chrome/tw-page-ruler.tsx +386 -0
  77. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +150 -14
  78. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +128 -0
  79. package/src/ui-tailwind/editor-surface/perf-probe.ts +179 -0
  80. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +46 -7
  81. package/src/ui-tailwind/editor-surface/pm-contextual-ui.ts +31 -0
  82. package/src/ui-tailwind/editor-surface/pm-decorations.ts +35 -0
  83. package/src/ui-tailwind/editor-surface/pm-position-map.ts +3 -3
  84. package/src/ui-tailwind/editor-surface/pm-schema.ts +186 -13
  85. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +191 -68
  86. package/src/ui-tailwind/editor-surface/search-plugin.ts +19 -68
  87. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +51 -0
  88. package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +11 -0
  89. package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +7 -1
  90. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +528 -85
  91. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +0 -1
  92. package/src/ui-tailwind/index.ts +2 -1
  93. package/src/ui-tailwind/page-chrome-model.ts +27 -0
  94. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +277 -147
  95. package/src/ui-tailwind/review/tw-health-panel.tsx +31 -2
  96. package/src/ui-tailwind/review/tw-review-rail.tsx +8 -8
  97. package/src/ui-tailwind/review/tw-revision-sidebar.tsx +15 -15
  98. package/src/ui-tailwind/theme/editor-theme.css +127 -0
  99. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +4 -0
  100. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +829 -12
  101. package/src/ui-tailwind/tw-review-workspace.tsx +1238 -42
  102. package/src/validation/compatibility-engine.ts +119 -24
  103. package/src/validation/compatibility-report.ts +1 -0
  104. package/src/validation/diagnostics.ts +1 -0
  105. package/src/validation/docx-comment-proof.ts +707 -0
@@ -52,34 +52,34 @@ export function TwReviewRail(props: TwReviewRailProps) {
52
52
  return (
53
53
  <aside
54
54
  aria-label="Review rail"
55
- className="flex w-[340px] shrink-0 flex-col border-l border-border bg-canvas"
55
+ className="flex w-[304px] shrink-0 flex-col border-l border-border bg-canvas"
56
56
  >
57
57
  <Tabs.Root
58
58
  value={props.activeTab}
59
59
  onValueChange={(v: string) => props.onActiveTabChange(v as ReviewRailTab)}
60
60
  className="flex flex-1 flex-col min-h-0"
61
61
  >
62
- <Tabs.List className="flex shrink-0 border-b border-border">
62
+ <Tabs.List className="flex shrink-0 border-b border-border px-2">
63
63
  <Tabs.Trigger
64
64
  value="comments"
65
- className={`flex-1 py-2 text-xs text-tertiary transition-colors data-[state=active]:text-primary data-[state=active]:shadow-[inset_0_-2px_0_var(--color-accent)] outline-none ${focusRingClass}`}
65
+ className={`flex-1 py-2 text-xs text-tertiary font-medium transition-colors data-[state=active]:text-primary data-[state=active]:font-semibold data-[state=active]:shadow-[inset_0_-2px_0_var(--color-accent)] outline-none ${focusRingClass}`}
66
66
  >
67
67
  Comments{" "}
68
- <span className="text-tertiary">{props.comments.totalCount}</span>
68
+ <span className="ml-1 inline-flex min-w-[14px] items-center justify-center rounded-full bg-subtle px-1.5 py-px text-[10px] font-medium text-tertiary">{props.comments.totalCount}</span>
69
69
  </Tabs.Trigger>
70
70
  <Tabs.Trigger
71
71
  value="changes"
72
- className={`flex-1 py-2 text-xs text-tertiary transition-colors data-[state=active]:text-primary data-[state=active]:shadow-[inset_0_-2px_0_var(--color-accent)] outline-none ${focusRingClass}`}
72
+ className={`flex-1 py-2 text-xs text-tertiary font-medium transition-colors data-[state=active]:text-primary data-[state=active]:font-semibold data-[state=active]:shadow-[inset_0_-2px_0_var(--color-accent)] outline-none ${focusRingClass}`}
73
73
  >
74
74
  Changes{" "}
75
- <span className="text-tertiary">{props.trackedChanges.totalCount}</span>
75
+ <span className="ml-1 inline-flex min-w-[14px] items-center justify-center rounded-full bg-subtle px-1.5 py-px text-[10px] font-medium text-tertiary">{props.trackedChanges.totalCount}</span>
76
76
  </Tabs.Trigger>
77
77
  {/* Health moved to toolbar popover */}
78
78
  </Tabs.List>
79
79
 
80
80
  <ScrollArea.Root className="flex-1 min-h-0">
81
81
  <ScrollArea.Viewport className="h-full w-full">
82
- <Tabs.Content value="comments" className="p-3 outline-none">
82
+ <Tabs.Content value="comments" className="p-2.5 outline-none">
83
83
  <TwCommentSidebar
84
84
  currentUserId={props.currentUserId}
85
85
  comments={props.comments}
@@ -92,7 +92,7 @@ export function TwReviewRail(props: TwReviewRailProps) {
92
92
  />
93
93
  </Tabs.Content>
94
94
 
95
- <Tabs.Content value="changes" className="p-3 outline-none">
95
+ <Tabs.Content value="changes" className="p-2.5 outline-none">
96
96
  <TwRevisionSidebar
97
97
  trackedChanges={props.trackedChanges}
98
98
  markupDisplay={props.markupDisplay}
@@ -28,16 +28,16 @@ export function TwRevisionSidebar(props: TwRevisionSidebarProps) {
28
28
 
29
29
  return (
30
30
  <div className="outline-none">
31
- <p className="text-xs text-tertiary mb-3">
31
+ <p className="mb-2 text-[10px] text-tertiary">
32
32
  {trackedChanges.pendingChangeIds.length} active · {trackedChanges.acceptedChangeIds.length} accepted · {trackedChanges.preserveOnlyChangeIds.length} preserve-only
33
33
  </p>
34
34
 
35
35
  {/* Bulk actions */}
36
- <div className="flex gap-1.5 mb-3">
36
+ <div className="mb-2 flex gap-1">
37
37
  <button
38
38
  type="button"
39
39
  disabled={actionablePendingCount === 0}
40
- className="inline-flex items-center gap-1 rounded-md px-2.5 py-1.5 text-xs font-semibold text-accent hover:bg-accent-soft transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
40
+ className="inline-flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-semibold text-accent hover:bg-accent-soft transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
41
41
  onClick={props.onAcceptAllChanges}
42
42
  >
43
43
  Accept all ({actionablePendingCount})
@@ -45,7 +45,7 @@ export function TwRevisionSidebar(props: TwRevisionSidebarProps) {
45
45
  <button
46
46
  type="button"
47
47
  disabled={actionablePendingCount === 0}
48
- className="inline-flex items-center gap-1 rounded-md px-2.5 py-1.5 text-xs text-secondary hover:bg-surface transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
48
+ className="inline-flex items-center gap-1 rounded-md px-2 py-1 text-[10px] text-secondary hover:bg-surface transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
49
49
  onClick={props.onRejectAllChanges}
50
50
  >
51
51
  Reject all
@@ -76,14 +76,14 @@ export function TwRevisionSidebar(props: TwRevisionSidebarProps) {
76
76
  : rev.kind === "deletion" ? "bg-danger"
77
77
  : "bg-tertiary"
78
78
  }`} />
79
- <div className="p-2.5 flex-1 min-w-0">
80
- <div className="flex items-start justify-between gap-2 mb-1">
81
- <span className="text-sm font-medium text-primary">{rev.anchorLabel}</span>
79
+ <div className="p-2 flex-1 min-w-0">
80
+ <div className="mb-0.5 flex items-start justify-between gap-2">
81
+ <span className="text-[11px] font-medium text-primary">{rev.anchorLabel}</span>
82
82
  <RevisionBadge status={rev.status} actionability={rev.actionability} />
83
83
  </div>
84
- <p className="text-xs text-tertiary mb-1">{rev.authorId} · {rev.createdAt}</p>
84
+ <p className="mb-1 text-[10px] text-tertiary">{rev.authorId} · {rev.createdAt}</p>
85
85
  {rev.excerpt ? (
86
- <p className={`text-sm ${
86
+ <p className={`text-[11px] ${
87
87
  rev.kind === "insertion" ? "text-insert"
88
88
  : rev.kind === "deletion" ? "text-danger line-through"
89
89
  : "text-secondary"
@@ -91,18 +91,18 @@ export function TwRevisionSidebar(props: TwRevisionSidebarProps) {
91
91
  {rev.excerpt}
92
92
  </p>
93
93
  ) : (
94
- <p className="text-sm text-secondary">{rev.label}</p>
94
+ <p className="text-[11px] text-secondary">{rev.label}</p>
95
95
  )}
96
96
  {rev.detail ? (
97
- <p className="text-xs text-secondary mt-1">{rev.detail}</p>
97
+ <p className="mt-1 text-[10px] text-secondary">{rev.detail}</p>
98
98
  ) : null}
99
- <div className="flex gap-1.5 mt-2">
99
+ <div className="mt-1.5 flex gap-1">
100
100
  {rev.actionability === "actionable" ? (
101
101
  <>
102
102
  <button
103
103
  type="button"
104
104
  disabled={!rev.canAccept || rev.status === "accepted"}
105
- className="inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs text-insert hover:bg-insert-soft transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
105
+ className="inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-[10px] text-insert hover:bg-insert-soft transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
106
106
  onClick={(e) => {
107
107
  e.stopPropagation();
108
108
  props.onAcceptRevision?.(rev.revisionId);
@@ -113,7 +113,7 @@ export function TwRevisionSidebar(props: TwRevisionSidebarProps) {
113
113
  <button
114
114
  type="button"
115
115
  disabled={!rev.canReject || rev.status === "rejected"}
116
- className="inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs text-danger hover:bg-delete-soft transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
116
+ className="inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-[10px] text-danger hover:bg-delete-soft transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
117
117
  onClick={(e) => {
118
118
  e.stopPropagation();
119
119
  props.onRejectRevision?.(rev.revisionId);
@@ -123,7 +123,7 @@ export function TwRevisionSidebar(props: TwRevisionSidebarProps) {
123
123
  </button>
124
124
  </>
125
125
  ) : (
126
- <span className="text-xs text-tertiary px-2 py-1">Preserve-only</span>
126
+ <span className="px-1.5 py-0.5 text-[10px] text-tertiary">Preserve-only</span>
127
127
  )}
128
128
  </div>
129
129
  </div>
@@ -68,6 +68,11 @@
68
68
  --font-legal-serif: var(--font-legal-serif, "Source Serif 4", "Georgia", "Times New Roman", serif);
69
69
  --font-legal-sans: var(--font-legal-sans, "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);
70
70
  --font-legal-mono: "JetBrains Mono", "SF Mono", "Fira Code", "Consolas", monospace;
71
+
72
+ /* Page-mode document surface */
73
+ --color-page-shadow: rgba(16, 24, 40, 0.06);
74
+ --color-page-border: rgba(0, 0, 0, 0.04);
75
+ --color-page-bg: #ffffff;
71
76
  }
72
77
 
73
78
  /* ─── Dark mode overrides ─── */
@@ -108,6 +113,96 @@
108
113
 
109
114
  --color-shadow: rgba(0, 0, 0, 0.32);
110
115
  --color-shadow-strong: rgba(0, 0, 0, 0.48);
116
+
117
+ --color-page-shadow: rgba(0, 0, 0, 0.18);
118
+ --color-page-border: rgba(255, 255, 255, 0.06);
119
+ --color-page-bg: #1e1e1e;
120
+ }
121
+
122
+ /*
123
+ * ─── Font substitution map ───
124
+ *
125
+ * Common Word font families mapped to redistributable metric-compatible or
126
+ * open substitutes. These CSS custom properties are consumed by the
127
+ * ProseMirror surface font_family mark and by the page-mode typography
128
+ * layer. Hosts can override individual properties to supply their own
129
+ * licensed fonts.
130
+ *
131
+ * Strategy:
132
+ * 1. Use metric-compatible open substitutes where available
133
+ * (e.g. Liberation Serif for Times New Roman, Carlito for Calibri).
134
+ * 2. Fall back through system-installed metric-compatible fonts
135
+ * (e.g. Cambria → system serif, Calibri → system sans).
136
+ * 3. Final fallback to the editor's legal-serif or legal-sans role font.
137
+ *
138
+ * This pipeline does NOT ship proprietary Microsoft fonts. It uses only
139
+ * redistributable or system-installed families.
140
+ */
141
+ :root {
142
+ /* Serif substitutions */
143
+ --wre-font-times-new-roman: "Liberation Serif", "Times New Roman", "Source Serif 4", "Georgia", serif;
144
+ --wre-font-cambria: "Caladea", "Cambria", "Source Serif 4", "Georgia", serif;
145
+ --wre-font-garamond: "EB Garamond", "Garamond", "Source Serif 4", serif;
146
+ --wre-font-book-antiqua: "Palatino Linotype", "Book Antiqua", "Source Serif 4", serif;
147
+ --wre-font-georgia: "Georgia", "Source Serif 4", serif;
148
+
149
+ /* Sans-serif substitutions */
150
+ --wre-font-calibri: "Carlito", "Calibri", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
151
+ --wre-font-arial: "Liberation Sans", "Arial", "Inter", sans-serif;
152
+ --wre-font-helvetica: "Liberation Sans", "Helvetica Neue", "Helvetica", "Inter", sans-serif;
153
+ --wre-font-verdana: "Verdana", "Inter", sans-serif;
154
+ --wre-font-tahoma: "Tahoma", "Inter", sans-serif;
155
+ --wre-font-segoe-ui: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
156
+ --wre-font-century-gothic: "URW Gothic", "Century Gothic", "Inter", sans-serif;
157
+
158
+ /* Monospace substitutions */
159
+ --wre-font-courier-new: "Liberation Mono", "Courier New", "JetBrains Mono", monospace;
160
+ --wre-font-consolas: "JetBrains Mono", "Consolas", "SF Mono", monospace;
161
+ }
162
+
163
+ /* ─── Page-mode typography ─── */
164
+ .wre-page-surface {
165
+ font-family: var(--font-legal-serif);
166
+ font-size: 15px;
167
+ line-height: 1.6;
168
+ color: var(--color-primary);
169
+ -webkit-font-smoothing: antialiased;
170
+ -moz-osx-font-smoothing: grayscale;
171
+ text-rendering: optimizeLegibility;
172
+ font-kerning: normal;
173
+ font-variant-ligatures: common-ligatures;
174
+ }
175
+
176
+ .wre-page-surface p {
177
+ margin: 0 0 0.5em 0;
178
+ orphans: 2;
179
+ widows: 2;
180
+ }
181
+
182
+ /* Page chrome — shadow, border, and background for the page-mode document panel */
183
+ .wre-page-chrome {
184
+ background: var(--color-page-bg);
185
+ border: 1px solid var(--color-page-border);
186
+ border-radius: 2px;
187
+ box-shadow: 0 1px 4px var(--color-page-shadow);
188
+ }
189
+
190
+ /* Canvas-mode typography — lighter, review-first baseline */
191
+ .wre-canvas-surface {
192
+ font-family: var(--font-legal-serif);
193
+ font-size: 15px;
194
+ line-height: 1.6;
195
+ color: var(--color-primary);
196
+ -webkit-font-smoothing: antialiased;
197
+ -moz-osx-font-smoothing: grayscale;
198
+ text-rendering: optimizeLegibility;
199
+ font-kerning: normal;
200
+ }
201
+
202
+ /* Wide-table readability in page mode */
203
+ .wre-page-surface table {
204
+ font-size: 14px;
205
+ line-height: 1.45;
111
206
  }
112
207
 
113
208
  /* ─── Base resets ─── */
@@ -171,6 +266,10 @@
171
266
  margin: 0 0 0.5em 0;
172
267
  }
173
268
 
269
+ .prosemirror-surface .ProseMirror p[data-numbered="true"][data-list-continuation="true"] {
270
+ margin-bottom: 0.25em;
271
+ }
272
+
174
273
  .prosemirror-surface .ProseMirror [data-node-type="opaque_block"] {
175
274
  user-select: none;
176
275
  cursor: default;
@@ -188,3 +287,31 @@
188
287
  .prosemirror-surface .ProseMirror ::selection {
189
288
  background: var(--color-accent-soft);
190
289
  }
290
+
291
+ .prosemirror-surface .ProseMirror .ProseMirror-selectednode {
292
+ outline: none;
293
+ background-color: var(--color-accent-soft);
294
+ border-radius: 4px;
295
+ }
296
+
297
+ /*
298
+ * ─── Preservation noise suppression ───
299
+ *
300
+ * Quiet-marker opaque inlines (w:proofErr, w:lastRenderedPageBreak, w:permStart,
301
+ * w:permEnd) are preserved in the canonical document for export safety but
302
+ * contribute zero visual weight to the reading/editing surface. They remain in
303
+ * the DOM as zero-dimension spans so round-trip export is unaffected.
304
+ */
305
+ .prosemirror-surface .ProseMirror [data-inline-presentation="quiet-marker"] {
306
+ display: inline-block;
307
+ width: 0;
308
+ height: 0;
309
+ overflow: hidden;
310
+ vertical-align: baseline;
311
+ pointer-events: none;
312
+ }
313
+
314
+ /* ─── Page workspace zoom scaling ─── */
315
+ .wre-page-chrome[style*="scale"] {
316
+ will-change: transform;
317
+ }
@@ -1,6 +1,8 @@
1
1
  import React from "react";
2
2
  import * as Tooltip from "@radix-ui/react-tooltip";
3
3
 
4
+ import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-editor-selection";
5
+
4
6
  export interface TwToolbarIconButtonProps {
5
7
  icon: React.ComponentType<{ className?: string }>;
6
8
  label: string;
@@ -19,7 +21,9 @@ export function TwToolbarIconButton(props: TwToolbarIconButtonProps) {
19
21
  <Tooltip.Trigger asChild>
20
22
  <button
21
23
  type="button"
24
+ aria-label={props.label}
22
25
  disabled={props.disabled}
26
+ onMouseDown={preserveEditorSelectionMouseDown}
23
27
  onClick={props.onClick}
24
28
  className={[
25
29
  "inline-flex h-7 w-7 items-center justify-center rounded-md transition-colors outline-none",