@beyondwork/docx-react-component 1.0.80 → 1.0.82
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 +1 -1
- package/src/api/public-types.ts +12 -13
- package/src/api/v3/_runtime-handle.ts +4 -0
- package/src/api/v3/runtime/document.ts +61 -3
- package/src/api/v3/runtime/review.ts +55 -2
- package/src/api/v3/ui/chrome-composition.ts +10 -2
- package/src/io/normalize/normalize-text.ts +4 -1
- package/src/io/ooxml/parse-drawing.ts +4 -0
- package/src/model/canonical-document.ts +2 -0
- package/src/ui/WordReviewEditor.tsx +132 -3
- package/src/ui/editor-shell-view.tsx +1 -0
- package/src/ui-tailwind/chrome/editor-action-registry.ts +373 -0
- package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +59 -35
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +2 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +5 -1
- package/src/ui-tailwind/chrome/use-context-menu-controller.ts +15 -10
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +1 -0
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +256 -37
- package/src/ui-tailwind/editor-surface/pm-schema.ts +54 -1
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +31 -1
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +15 -0
- package/src/ui-tailwind/page-stack/tw-floating-image-layer.tsx +24 -5
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +35 -6
- package/src/ui-tailwind/page-stack/use-visible-block-range.ts +333 -43
- package/src/ui-tailwind/review-workspace/types.ts +1 -0
- package/src/ui-tailwind/review-workspace/use-page-markers.ts +273 -24
- package/src/ui-tailwind/review-workspace/use-workspace-composition.ts +46 -6
- package/src/ui-tailwind/theme/editor-theme.css +3 -5
- package/src/ui-tailwind/tw-review-workspace.tsx +117 -14
|
@@ -24,6 +24,8 @@ import type { EditorChromeMode } from "../../api/v3/ui/chrome-composition";
|
|
|
24
24
|
import type { ContextMenuGroupId } from "./tw-context-menu";
|
|
25
25
|
import type { ShortcutKey } from "./tw-shortcut-hint";
|
|
26
26
|
|
|
27
|
+
type ProductSectionBreakType = "nextPage" | "continuous" | "evenPage" | "oddPage";
|
|
28
|
+
|
|
27
29
|
/**
|
|
28
30
|
* Target kinds roughly mirror DESIGN-EDITOR.md §4 "Local context" cells.
|
|
29
31
|
* A contextmenu event resolves its target to one (or more) of these
|
|
@@ -66,11 +68,44 @@ export interface EditorActionDispatchContext {
|
|
|
66
68
|
* `WordReviewEditorRef`; the surface is intentionally minimal now.
|
|
67
69
|
*/
|
|
68
70
|
export interface EditorActionHostCallbacks {
|
|
71
|
+
// Command history
|
|
72
|
+
readonly onUndo?: () => void;
|
|
73
|
+
readonly onRedo?: () => void;
|
|
74
|
+
|
|
69
75
|
// Clipboard (always available — browser-native fallback acceptable)
|
|
70
76
|
readonly onCut?: () => void;
|
|
71
77
|
readonly onCopy?: () => void;
|
|
72
78
|
readonly onPaste?: () => void;
|
|
73
79
|
|
|
80
|
+
// Formatting / paragraph operations (plain text + table-cell targets)
|
|
81
|
+
readonly onToggleBold?: () => void;
|
|
82
|
+
readonly onToggleItalic?: () => void;
|
|
83
|
+
readonly onToggleUnderline?: () => void;
|
|
84
|
+
readonly onToggleStrikethrough?: () => void;
|
|
85
|
+
readonly onSetParagraphStyle?: (styleId: string) => void;
|
|
86
|
+
readonly onSetFontFamily?: (fontFamily: string) => void;
|
|
87
|
+
readonly onSetFontSize?: (fontSize: number) => void;
|
|
88
|
+
readonly onSetTextColor?: (color: string) => void;
|
|
89
|
+
readonly onSetHighlightColor?: (color: string | null) => void;
|
|
90
|
+
readonly onToggleBulletedList?: () => void;
|
|
91
|
+
readonly onToggleNumberedList?: () => void;
|
|
92
|
+
readonly onOutdent?: () => void;
|
|
93
|
+
readonly onIndent?: () => void;
|
|
94
|
+
readonly onSetAlignment?: (
|
|
95
|
+
alignment: "left" | "center" | "right" | "justify",
|
|
96
|
+
) => void;
|
|
97
|
+
readonly onInsertPageBreak?: () => void;
|
|
98
|
+
readonly onInsertSectionBreak?: (type: ProductSectionBreakType) => void;
|
|
99
|
+
readonly onInsertTable?: () => void;
|
|
100
|
+
readonly onInsertImage?: () => void;
|
|
101
|
+
readonly onAddComment?: () => void;
|
|
102
|
+
|
|
103
|
+
// Search / app-level host-delegated commands
|
|
104
|
+
readonly onFindRequested?: () => void;
|
|
105
|
+
readonly onReplaceRequested?: () => void;
|
|
106
|
+
readonly onPrintRequested?: () => void;
|
|
107
|
+
readonly onGoToRequested?: () => void;
|
|
108
|
+
|
|
74
109
|
// Tracked-change operations (suggestion target)
|
|
75
110
|
readonly onAcceptSuggestion?: () => void;
|
|
76
111
|
readonly onRejectSuggestion?: () => void;
|
|
@@ -87,6 +122,7 @@ export interface EditorActionHostCallbacks {
|
|
|
87
122
|
readonly onInsertColumnAfter?: () => void;
|
|
88
123
|
readonly onDeleteRow?: () => void;
|
|
89
124
|
readonly onDeleteColumn?: () => void;
|
|
125
|
+
readonly onDeleteTable?: () => void;
|
|
90
126
|
readonly onMergeCells?: () => void;
|
|
91
127
|
readonly onSplitCell?: () => void;
|
|
92
128
|
readonly onOpenTableProperties?: () => void;
|
|
@@ -114,6 +150,7 @@ export interface EditorActionHostCallbacks {
|
|
|
114
150
|
export interface EditorAction {
|
|
115
151
|
readonly id: string;
|
|
116
152
|
readonly label: string;
|
|
153
|
+
readonly description?: string;
|
|
117
154
|
readonly shortcut?: readonly ShortcutKey[];
|
|
118
155
|
readonly group: ContextMenuGroupId;
|
|
119
156
|
/** Targets for which this action is relevant. */
|
|
@@ -147,6 +184,7 @@ export interface EditorAction {
|
|
|
147
184
|
function mk<K extends keyof EditorActionHostCallbacks>(opts: {
|
|
148
185
|
id: string;
|
|
149
186
|
label: string;
|
|
187
|
+
description?: string;
|
|
150
188
|
shortcut?: readonly ShortcutKey[];
|
|
151
189
|
group: ContextMenuGroupId;
|
|
152
190
|
targetKinds: readonly TargetKind[];
|
|
@@ -158,6 +196,7 @@ function mk<K extends keyof EditorActionHostCallbacks>(opts: {
|
|
|
158
196
|
return {
|
|
159
197
|
id: opts.id,
|
|
160
198
|
label: opts.label,
|
|
199
|
+
...(opts.description ? { description: opts.description } : {}),
|
|
161
200
|
...(opts.shortcut ? { shortcut: opts.shortcut } : {}),
|
|
162
201
|
group: opts.group,
|
|
163
202
|
targetKinds: new Set(opts.targetKinds),
|
|
@@ -191,6 +230,7 @@ function mk<K extends keyof EditorActionHostCallbacks>(opts: {
|
|
|
191
230
|
function mkArg<A, K extends keyof EditorActionHostCallbacks>(opts: {
|
|
192
231
|
id: string;
|
|
193
232
|
label: string;
|
|
233
|
+
description?: string;
|
|
194
234
|
shortcut?: readonly ShortcutKey[];
|
|
195
235
|
group: ContextMenuGroupId;
|
|
196
236
|
targetKinds: readonly TargetKind[];
|
|
@@ -202,6 +242,7 @@ function mkArg<A, K extends keyof EditorActionHostCallbacks>(opts: {
|
|
|
202
242
|
return {
|
|
203
243
|
id: opts.id,
|
|
204
244
|
label: opts.label,
|
|
245
|
+
...(opts.description ? { description: opts.description } : {}),
|
|
205
246
|
...(opts.shortcut ? { shortcut: opts.shortcut } : {}),
|
|
206
247
|
group: opts.group,
|
|
207
248
|
targetKinds: new Set(opts.targetKinds),
|
|
@@ -219,7 +260,60 @@ function mkArg<A, K extends keyof EditorActionHostCallbacks>(opts: {
|
|
|
219
260
|
};
|
|
220
261
|
}
|
|
221
262
|
|
|
263
|
+
/**
|
|
264
|
+
* Build an important command that should remain visible as a disabled
|
|
265
|
+
* row when the host/runtime has not wired the implementation yet. Use
|
|
266
|
+
* sparingly for product-signpost commands such as Insert Image or
|
|
267
|
+
* Replace; most callback-less registry rows should stay hidden.
|
|
268
|
+
*/
|
|
269
|
+
function mkImportant<K extends keyof EditorActionHostCallbacks>(opts: {
|
|
270
|
+
id: string;
|
|
271
|
+
label: string;
|
|
272
|
+
description: string;
|
|
273
|
+
shortcut?: readonly ShortcutKey[];
|
|
274
|
+
group: ContextMenuGroupId;
|
|
275
|
+
targetKinds: readonly TargetKind[];
|
|
276
|
+
modes?: readonly EditorChromeMode[];
|
|
277
|
+
callback: K;
|
|
278
|
+
}): EditorAction {
|
|
279
|
+
return {
|
|
280
|
+
id: opts.id,
|
|
281
|
+
label: opts.label,
|
|
282
|
+
description: opts.description,
|
|
283
|
+
...(opts.shortcut ? { shortcut: opts.shortcut } : {}),
|
|
284
|
+
group: opts.group,
|
|
285
|
+
targetKinds: new Set(opts.targetKinds),
|
|
286
|
+
...(opts.modes ? { modes: new Set(opts.modes) } : {}),
|
|
287
|
+
when: (ctx) =>
|
|
288
|
+
typeof ctx.host[opts.callback] === "function" ? true : "disabled",
|
|
289
|
+
run: (ctx) => {
|
|
290
|
+
const fn = ctx.host[opts.callback] as (() => void) | undefined;
|
|
291
|
+
if (!fn) return;
|
|
292
|
+
fn();
|
|
293
|
+
ctx.dismiss();
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
222
298
|
export const EDITOR_ACTION_REGISTRY: readonly EditorAction[] = [
|
|
299
|
+
// -------- History --------
|
|
300
|
+
mk({
|
|
301
|
+
id: "undo",
|
|
302
|
+
label: "Undo",
|
|
303
|
+
shortcut: ["Mod", "Z"],
|
|
304
|
+
group: "misc",
|
|
305
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
306
|
+
callback: "onUndo",
|
|
307
|
+
}),
|
|
308
|
+
mk({
|
|
309
|
+
id: "redo",
|
|
310
|
+
label: "Redo",
|
|
311
|
+
shortcut: ["Mod", "Shift", "Z"],
|
|
312
|
+
group: "misc",
|
|
313
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
314
|
+
callback: "onRedo",
|
|
315
|
+
}),
|
|
316
|
+
|
|
223
317
|
// -------- Clipboard (always available across every target kind) --------
|
|
224
318
|
mk({
|
|
225
319
|
id: "cut",
|
|
@@ -271,6 +365,277 @@ export const EDITOR_ACTION_REGISTRY: readonly EditorAction[] = [
|
|
|
271
365
|
callback: "onPaste",
|
|
272
366
|
}),
|
|
273
367
|
|
|
368
|
+
// -------- Formatting / paragraph --------
|
|
369
|
+
mk({
|
|
370
|
+
id: "format-bold",
|
|
371
|
+
label: "Bold",
|
|
372
|
+
shortcut: ["Mod", "B"],
|
|
373
|
+
group: "formatting",
|
|
374
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
375
|
+
callback: "onToggleBold",
|
|
376
|
+
}),
|
|
377
|
+
mk({
|
|
378
|
+
id: "format-italic",
|
|
379
|
+
label: "Italic",
|
|
380
|
+
shortcut: ["Mod", "I"],
|
|
381
|
+
group: "formatting",
|
|
382
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
383
|
+
callback: "onToggleItalic",
|
|
384
|
+
}),
|
|
385
|
+
mk({
|
|
386
|
+
id: "format-underline",
|
|
387
|
+
label: "Underline",
|
|
388
|
+
shortcut: ["Mod", "U"],
|
|
389
|
+
group: "formatting",
|
|
390
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
391
|
+
callback: "onToggleUnderline",
|
|
392
|
+
}),
|
|
393
|
+
mk({
|
|
394
|
+
id: "format-strikethrough",
|
|
395
|
+
label: "Strikethrough",
|
|
396
|
+
group: "formatting",
|
|
397
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
398
|
+
callback: "onToggleStrikethrough",
|
|
399
|
+
}),
|
|
400
|
+
mkArg<string, "onSetParagraphStyle">({
|
|
401
|
+
id: "style-normal",
|
|
402
|
+
label: "Normal text",
|
|
403
|
+
group: "formatting",
|
|
404
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
405
|
+
callback: "onSetParagraphStyle",
|
|
406
|
+
payload: "Normal",
|
|
407
|
+
}),
|
|
408
|
+
mkArg<string, "onSetParagraphStyle">({
|
|
409
|
+
id: "style-heading-1",
|
|
410
|
+
label: "Heading 1",
|
|
411
|
+
shortcut: ["Mod", "Alt", "1"],
|
|
412
|
+
group: "formatting",
|
|
413
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
414
|
+
callback: "onSetParagraphStyle",
|
|
415
|
+
payload: "Heading1",
|
|
416
|
+
}),
|
|
417
|
+
mkArg<string, "onSetParagraphStyle">({
|
|
418
|
+
id: "style-heading-2",
|
|
419
|
+
label: "Heading 2",
|
|
420
|
+
shortcut: ["Mod", "Alt", "2"],
|
|
421
|
+
group: "formatting",
|
|
422
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
423
|
+
callback: "onSetParagraphStyle",
|
|
424
|
+
payload: "Heading2",
|
|
425
|
+
}),
|
|
426
|
+
mkArg<string, "onSetParagraphStyle">({
|
|
427
|
+
id: "style-heading-3",
|
|
428
|
+
label: "Heading 3",
|
|
429
|
+
shortcut: ["Mod", "Alt", "3"],
|
|
430
|
+
group: "formatting",
|
|
431
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
432
|
+
callback: "onSetParagraphStyle",
|
|
433
|
+
payload: "Heading3",
|
|
434
|
+
}),
|
|
435
|
+
mkArg<string, "onSetFontFamily">({
|
|
436
|
+
id: "font-family-aptos",
|
|
437
|
+
label: "Font: Aptos",
|
|
438
|
+
group: "formatting",
|
|
439
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
440
|
+
callback: "onSetFontFamily",
|
|
441
|
+
payload: "Aptos",
|
|
442
|
+
}),
|
|
443
|
+
mkArg<string, "onSetFontFamily">({
|
|
444
|
+
id: "font-family-calibri",
|
|
445
|
+
label: "Font: Calibri",
|
|
446
|
+
group: "formatting",
|
|
447
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
448
|
+
callback: "onSetFontFamily",
|
|
449
|
+
payload: "Calibri",
|
|
450
|
+
}),
|
|
451
|
+
mkArg<number, "onSetFontSize">({
|
|
452
|
+
id: "font-size-11",
|
|
453
|
+
label: "Font size: 11",
|
|
454
|
+
group: "formatting",
|
|
455
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
456
|
+
callback: "onSetFontSize",
|
|
457
|
+
payload: 11,
|
|
458
|
+
}),
|
|
459
|
+
mkArg<number, "onSetFontSize">({
|
|
460
|
+
id: "font-size-12",
|
|
461
|
+
label: "Font size: 12",
|
|
462
|
+
group: "formatting",
|
|
463
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
464
|
+
callback: "onSetFontSize",
|
|
465
|
+
payload: 12,
|
|
466
|
+
}),
|
|
467
|
+
mkArg<string, "onSetTextColor">({
|
|
468
|
+
id: "text-color-black",
|
|
469
|
+
label: "Text color: Black",
|
|
470
|
+
group: "formatting",
|
|
471
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
472
|
+
callback: "onSetTextColor",
|
|
473
|
+
payload: "#000000",
|
|
474
|
+
}),
|
|
475
|
+
mkArg<string, "onSetTextColor">({
|
|
476
|
+
id: "text-color-accent",
|
|
477
|
+
label: "Text color: Accent",
|
|
478
|
+
group: "formatting",
|
|
479
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
480
|
+
callback: "onSetTextColor",
|
|
481
|
+
payload: "#1F6B4F",
|
|
482
|
+
}),
|
|
483
|
+
mkArg<string | null, "onSetHighlightColor">({
|
|
484
|
+
id: "highlight-yellow",
|
|
485
|
+
label: "Highlight: Yellow",
|
|
486
|
+
group: "formatting",
|
|
487
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
488
|
+
callback: "onSetHighlightColor",
|
|
489
|
+
payload: "#FFF2A8",
|
|
490
|
+
}),
|
|
491
|
+
mkArg<string | null, "onSetHighlightColor">({
|
|
492
|
+
id: "highlight-clear",
|
|
493
|
+
label: "Clear highlight",
|
|
494
|
+
group: "formatting",
|
|
495
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
496
|
+
callback: "onSetHighlightColor",
|
|
497
|
+
payload: null,
|
|
498
|
+
}),
|
|
499
|
+
mk({
|
|
500
|
+
id: "list-bulleted",
|
|
501
|
+
label: "Bulleted list",
|
|
502
|
+
group: "formatting",
|
|
503
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
504
|
+
callback: "onToggleBulletedList",
|
|
505
|
+
}),
|
|
506
|
+
mk({
|
|
507
|
+
id: "list-numbered",
|
|
508
|
+
label: "Numbered list",
|
|
509
|
+
group: "formatting",
|
|
510
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
511
|
+
callback: "onToggleNumberedList",
|
|
512
|
+
}),
|
|
513
|
+
mk({
|
|
514
|
+
id: "paragraph-outdent",
|
|
515
|
+
label: "Decrease indent",
|
|
516
|
+
group: "formatting",
|
|
517
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
518
|
+
callback: "onOutdent",
|
|
519
|
+
}),
|
|
520
|
+
mk({
|
|
521
|
+
id: "paragraph-indent",
|
|
522
|
+
label: "Increase indent",
|
|
523
|
+
group: "formatting",
|
|
524
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
525
|
+
callback: "onIndent",
|
|
526
|
+
}),
|
|
527
|
+
mkArg<"left" | "center" | "right" | "justify", "onSetAlignment">({
|
|
528
|
+
id: "align-left",
|
|
529
|
+
label: "Align left",
|
|
530
|
+
group: "formatting",
|
|
531
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
532
|
+
callback: "onSetAlignment",
|
|
533
|
+
payload: "left",
|
|
534
|
+
}),
|
|
535
|
+
mkArg<"left" | "center" | "right" | "justify", "onSetAlignment">({
|
|
536
|
+
id: "align-center",
|
|
537
|
+
label: "Align center",
|
|
538
|
+
group: "formatting",
|
|
539
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
540
|
+
callback: "onSetAlignment",
|
|
541
|
+
payload: "center",
|
|
542
|
+
}),
|
|
543
|
+
mkArg<"left" | "center" | "right" | "justify", "onSetAlignment">({
|
|
544
|
+
id: "align-right",
|
|
545
|
+
label: "Align right",
|
|
546
|
+
group: "formatting",
|
|
547
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
548
|
+
callback: "onSetAlignment",
|
|
549
|
+
payload: "right",
|
|
550
|
+
}),
|
|
551
|
+
mkArg<"left" | "center" | "right" | "justify", "onSetAlignment">({
|
|
552
|
+
id: "align-justify",
|
|
553
|
+
label: "Justify",
|
|
554
|
+
group: "formatting",
|
|
555
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
556
|
+
callback: "onSetAlignment",
|
|
557
|
+
payload: "justify",
|
|
558
|
+
}),
|
|
559
|
+
mk({
|
|
560
|
+
id: "insert-page-break",
|
|
561
|
+
label: "Insert page break",
|
|
562
|
+
shortcut: ["Mod", "Enter"],
|
|
563
|
+
group: "misc",
|
|
564
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
565
|
+
callback: "onInsertPageBreak",
|
|
566
|
+
}),
|
|
567
|
+
mkArg<ProductSectionBreakType, "onInsertSectionBreak">({
|
|
568
|
+
id: "insert-section-break-next-page",
|
|
569
|
+
label: "Insert section break",
|
|
570
|
+
description: "Next page section break",
|
|
571
|
+
group: "misc",
|
|
572
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
573
|
+
callback: "onInsertSectionBreak",
|
|
574
|
+
payload: "nextPage",
|
|
575
|
+
}),
|
|
576
|
+
mkImportant({
|
|
577
|
+
id: "insert-image",
|
|
578
|
+
label: "Insert image…",
|
|
579
|
+
description: "Needs a host file picker before it can run.",
|
|
580
|
+
group: "misc",
|
|
581
|
+
targetKinds: [],
|
|
582
|
+
callback: "onInsertImage",
|
|
583
|
+
}),
|
|
584
|
+
mk({
|
|
585
|
+
id: "insert-table",
|
|
586
|
+
label: "Insert table",
|
|
587
|
+
group: "table",
|
|
588
|
+
targetKinds: ["plain-text"],
|
|
589
|
+
callback: "onInsertTable",
|
|
590
|
+
}),
|
|
591
|
+
mk({
|
|
592
|
+
id: "comment-add",
|
|
593
|
+
label: "Add comment",
|
|
594
|
+
group: "comment",
|
|
595
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
596
|
+
callback: "onAddComment",
|
|
597
|
+
}),
|
|
598
|
+
|
|
599
|
+
// -------- Search / navigation / app-level commands --------
|
|
600
|
+
// Empty targetKinds keeps these out of right-click local menus. The
|
|
601
|
+
// global command palette still projects them from the shared registry.
|
|
602
|
+
mkImportant({
|
|
603
|
+
id: "find",
|
|
604
|
+
label: "Find…",
|
|
605
|
+
description: "Host search panel is not wired.",
|
|
606
|
+
shortcut: ["Mod", "F"],
|
|
607
|
+
group: "misc",
|
|
608
|
+
targetKinds: [],
|
|
609
|
+
callback: "onFindRequested",
|
|
610
|
+
}),
|
|
611
|
+
mkImportant({
|
|
612
|
+
id: "replace",
|
|
613
|
+
label: "Replace…",
|
|
614
|
+
description: "Host replace panel is not wired.",
|
|
615
|
+
shortcut: ["Ctrl", "H"],
|
|
616
|
+
group: "misc",
|
|
617
|
+
targetKinds: [],
|
|
618
|
+
callback: "onReplaceRequested",
|
|
619
|
+
}),
|
|
620
|
+
mkImportant({
|
|
621
|
+
id: "go-to",
|
|
622
|
+
label: "Go to…",
|
|
623
|
+
description: "Host navigation panel is not wired.",
|
|
624
|
+
shortcut: ["Ctrl", "G"],
|
|
625
|
+
group: "misc",
|
|
626
|
+
targetKinds: [],
|
|
627
|
+
callback: "onGoToRequested",
|
|
628
|
+
}),
|
|
629
|
+
mkImportant({
|
|
630
|
+
id: "print",
|
|
631
|
+
label: "Print…",
|
|
632
|
+
description: "Host print/export flow is not wired.",
|
|
633
|
+
shortcut: ["Mod", "P"],
|
|
634
|
+
group: "misc",
|
|
635
|
+
targetKinds: [],
|
|
636
|
+
callback: "onPrintRequested",
|
|
637
|
+
}),
|
|
638
|
+
|
|
274
639
|
// -------- Suggestion / tracked change --------
|
|
275
640
|
mk({
|
|
276
641
|
id: "accept-suggestion",
|
|
@@ -379,6 +744,14 @@ export const EDITOR_ACTION_REGISTRY: readonly EditorAction[] = [
|
|
|
379
744
|
targetKinds: ["table-column", "table-whole"],
|
|
380
745
|
callback: "onDeleteColumn",
|
|
381
746
|
}),
|
|
747
|
+
// ── Whole-table destructive op ──
|
|
748
|
+
mk({
|
|
749
|
+
id: "table-delete-table",
|
|
750
|
+
label: "Delete table",
|
|
751
|
+
group: "table",
|
|
752
|
+
targetKinds: ["table-whole"],
|
|
753
|
+
callback: "onDeleteTable",
|
|
754
|
+
}),
|
|
382
755
|
// ── Whole-table — properties / borders surface at any tier so
|
|
383
756
|
// they are reachable from a single right-click in a cell ──
|
|
384
757
|
mk({
|
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Progressive-disclosure discipline (designsystem.md §2.1 principle 4):
|
|
14
14
|
* - Actions without a wired callback are hidden, not rendered
|
|
15
|
-
* disabled
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* disabled unless the registry marks them as important signposts
|
|
16
|
+
* by returning `"disabled"` from `when()`. Those rows stay visible
|
|
17
|
+
* with an explanation so users can see that the product recognizes
|
|
18
|
+
* the workflow even when the host has not wired it yet.
|
|
18
19
|
* - Mode-filtered actions (e.g. `scope-open-card` is
|
|
19
20
|
* workflow/review-only) only appear when the composition mode
|
|
20
21
|
* allows them.
|
|
@@ -33,7 +34,6 @@ import type {
|
|
|
33
34
|
CommandPaletteGroup,
|
|
34
35
|
CommandPaletteItem,
|
|
35
36
|
} from "./tw-command-palette";
|
|
36
|
-
import type { ContextMenuGroupId } from "./tw-context-menu";
|
|
37
37
|
import type { ShortcutKey } from "./tw-shortcut-hint";
|
|
38
38
|
|
|
39
39
|
/**
|
|
@@ -90,43 +90,63 @@ export function formatShortcutForPalette(
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
93
|
+
* Product-facing command palette buckets. These are intentionally not
|
|
94
|
+
* the context-menu `ContextMenuGroupId`s: right-click menus group by
|
|
95
|
+
* local action family, while the palette groups by user intent.
|
|
96
96
|
*/
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
type ProductPaletteGroupId =
|
|
98
|
+
| "commands"
|
|
99
|
+
| "search"
|
|
100
|
+
| "navigation"
|
|
101
|
+
| "mode"
|
|
102
|
+
| "diagnostics";
|
|
103
|
+
|
|
104
|
+
const GROUP_LABELS: Record<ProductPaletteGroupId, string> = {
|
|
105
|
+
commands: "Commands",
|
|
106
|
+
search: "Search",
|
|
107
|
+
navigation: "Navigation",
|
|
108
|
+
mode: "Mode",
|
|
109
|
+
diagnostics: "Diagnostics",
|
|
104
110
|
};
|
|
105
111
|
|
|
106
112
|
/**
|
|
107
|
-
* Stable group order
|
|
108
|
-
* designsystem.md §2.1 (clipboard first because it's always available,
|
|
109
|
-
* review + comment next because they're the primary legal-review
|
|
110
|
-
* actions, table + formatting after, misc last).
|
|
113
|
+
* Stable product group order per editor-product Slice 3.
|
|
111
114
|
*/
|
|
112
|
-
const GROUP_ORDER: readonly
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"misc",
|
|
115
|
+
const GROUP_ORDER: readonly ProductPaletteGroupId[] = [
|
|
116
|
+
"commands",
|
|
117
|
+
"search",
|
|
118
|
+
"navigation",
|
|
119
|
+
"mode",
|
|
120
|
+
"diagnostics",
|
|
119
121
|
];
|
|
120
122
|
|
|
121
|
-
function
|
|
123
|
+
function actionAvailability(
|
|
122
124
|
action: EditorAction,
|
|
123
125
|
ctx: EditorActionDispatchContext,
|
|
124
|
-
):
|
|
126
|
+
): false | "enabled" | "disabled" {
|
|
125
127
|
const verdict = action.when ? action.when(ctx) : true;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
if (verdict === false) return false;
|
|
129
|
+
return verdict === "disabled" ? "disabled" : "enabled";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function paletteGroupForAction(action: EditorAction): ProductPaletteGroupId {
|
|
133
|
+
switch (action.id) {
|
|
134
|
+
case "find":
|
|
135
|
+
case "replace":
|
|
136
|
+
return "search";
|
|
137
|
+
case "go-to":
|
|
138
|
+
case "jump-to-comment-in-rail":
|
|
139
|
+
case "scope-jump-in-rail":
|
|
140
|
+
case "scope-open-card":
|
|
141
|
+
return "navigation";
|
|
142
|
+
case "scope-mark-resolved":
|
|
143
|
+
return "mode";
|
|
144
|
+
case "object-info":
|
|
145
|
+
case "print":
|
|
146
|
+
return "diagnostics";
|
|
147
|
+
default:
|
|
148
|
+
return "commands";
|
|
149
|
+
}
|
|
130
150
|
}
|
|
131
151
|
|
|
132
152
|
export interface BuildPaletteGroupsInput {
|
|
@@ -145,15 +165,18 @@ export function buildPaletteGroupsFromRegistry(
|
|
|
145
165
|
dismiss: input.dismiss,
|
|
146
166
|
};
|
|
147
167
|
|
|
148
|
-
const byGroup = new Map<
|
|
168
|
+
const byGroup = new Map<ProductPaletteGroupId, CommandPaletteItem[]>();
|
|
149
169
|
|
|
150
170
|
for (const action of EDITOR_ACTION_REGISTRY) {
|
|
151
171
|
if (action.modes && !action.modes.has(input.mode)) continue;
|
|
152
|
-
|
|
172
|
+
const availability = actionAvailability(action, ctx);
|
|
173
|
+
if (availability === false) continue;
|
|
153
174
|
|
|
154
175
|
const item: CommandPaletteItem = {
|
|
155
176
|
id: action.id,
|
|
156
177
|
label: action.label,
|
|
178
|
+
...(action.description ? { description: action.description } : {}),
|
|
179
|
+
...(availability === "disabled" ? { disabled: true } : {}),
|
|
157
180
|
// Chrome Closure Pass · Task 4 (designsystem.md §6.25) —
|
|
158
181
|
// preserve the registry shortcut so the palette row shows
|
|
159
182
|
// the same hint as the matching context-menu / tooltip.
|
|
@@ -163,9 +186,10 @@ export function buildPaletteGroupsFromRegistry(
|
|
|
163
186
|
onInvoke: () => action.run(ctx),
|
|
164
187
|
};
|
|
165
188
|
|
|
166
|
-
const
|
|
189
|
+
const groupId = paletteGroupForAction(action);
|
|
190
|
+
const bucket = byGroup.get(groupId) ?? [];
|
|
167
191
|
bucket.push(item);
|
|
168
|
-
byGroup.set(
|
|
192
|
+
byGroup.set(groupId, bucket);
|
|
169
193
|
}
|
|
170
194
|
|
|
171
195
|
const groups: CommandPaletteGroup[] = [];
|
|
@@ -75,6 +75,7 @@ export interface TwSelectionToolHostProps {
|
|
|
75
75
|
onDistributeColumnsEvenly?: () => void;
|
|
76
76
|
onSetTableAlignment?: (alignment: "left" | "center" | "right") => void;
|
|
77
77
|
onSetCellVerticalAlign?: (align: "top" | "center" | "bottom") => void;
|
|
78
|
+
onOpenTableMore?: (coords: { clientX: number; clientY: number }) => void;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
/**
|
|
@@ -299,6 +300,7 @@ function renderTool(
|
|
|
299
300
|
onDistributeColumnsEvenly={props.onDistributeColumnsEvenly}
|
|
300
301
|
onSetTableAlignment={props.onSetTableAlignment}
|
|
301
302
|
onSetCellVerticalAlign={props.onSetCellVerticalAlign}
|
|
303
|
+
onOpenTableMore={props.onOpenTableMore}
|
|
302
304
|
/>
|
|
303
305
|
);
|
|
304
306
|
case "comment-thread":
|
|
@@ -35,6 +35,7 @@ export interface TwSelectionToolStructureProps {
|
|
|
35
35
|
onDistributeColumnsEvenly?: () => void;
|
|
36
36
|
onSetTableAlignment?: (alignment: "left" | "center" | "right") => void;
|
|
37
37
|
onSetCellVerticalAlign?: (align: "top" | "center" | "bottom") => void;
|
|
38
|
+
onOpenTableMore?: (coords: { clientX: number; clientY: number }) => void;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export function TwSelectionToolStructure(props: TwSelectionToolStructureProps) {
|
|
@@ -58,7 +59,10 @@ export function TwSelectionToolStructure(props: TwSelectionToolStructureProps) {
|
|
|
58
59
|
return (
|
|
59
60
|
<TwTableContextToolbar
|
|
60
61
|
disabled={!props.model.canMutate}
|
|
61
|
-
tableContext={props.model.activeTable ?? null}
|
|
62
|
+
tableContext={props.model.activeTable ?? null}
|
|
63
|
+
tableStyles={props.model.tableStyles ?? []}
|
|
64
|
+
compact={true}
|
|
65
|
+
onOpenMore={props.onOpenTableMore}
|
|
62
66
|
onSetTableStyle={props.onSetTableStyle}
|
|
63
67
|
onAddRowBefore={props.onAddRowBefore}
|
|
64
68
|
onAddRowAfter={props.onAddRowAfter}
|
|
@@ -58,6 +58,18 @@ function tableContextToKinds(
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
function mergeTableTierKinds(
|
|
62
|
+
kinds: readonly TargetKind[],
|
|
63
|
+
tableContext: TableStructureContextSnapshot | null | undefined,
|
|
64
|
+
): readonly TargetKind[] {
|
|
65
|
+
if (!kinds.includes("table-cell") || !tableContext) return kinds;
|
|
66
|
+
const merged = [...kinds];
|
|
67
|
+
for (const k of tableContextToKinds(tableContext)) {
|
|
68
|
+
if (!merged.includes(k)) merged.push(k);
|
|
69
|
+
}
|
|
70
|
+
return merged;
|
|
71
|
+
}
|
|
72
|
+
|
|
61
73
|
export interface ContextMenuRequest {
|
|
62
74
|
readonly clientX: number;
|
|
63
75
|
readonly clientY: number;
|
|
@@ -149,14 +161,7 @@ export function useContextMenuController(
|
|
|
149
161
|
// is in a table cell. Cell-level ops carry every table-* kind
|
|
150
162
|
// (so they are still visible) and tier-only ops (delete row /
|
|
151
163
|
// delete column) gate on the matching kind.
|
|
152
|
-
const
|
|
153
|
-
baseKinds.includes("table-cell") && tableContext
|
|
154
|
-
? tableContextToKinds(tableContext)
|
|
155
|
-
: [];
|
|
156
|
-
const merged = [...baseKinds];
|
|
157
|
-
for (const k of tierKinds) {
|
|
158
|
-
if (!merged.includes(k)) merged.push(k);
|
|
159
|
-
}
|
|
164
|
+
const merged = mergeTableTierKinds(baseKinds, tableContext);
|
|
160
165
|
const entries = buildEntries(merged);
|
|
161
166
|
// Progressive disclosure: if no actions resolve (every host callback
|
|
162
167
|
// absent + no scope-anchor + etc), don't open an empty menu. Let
|
|
@@ -180,7 +185,7 @@ export function useContextMenuController(
|
|
|
180
185
|
clientY: number;
|
|
181
186
|
kinds: readonly TargetKind[];
|
|
182
187
|
}) => {
|
|
183
|
-
const entries = buildEntries(args.kinds);
|
|
188
|
+
const entries = buildEntries(mergeTableTierKinds(args.kinds, tableContext));
|
|
184
189
|
if (entries.length === 0) return;
|
|
185
190
|
setState({
|
|
186
191
|
open: true,
|
|
@@ -189,7 +194,7 @@ export function useContextMenuController(
|
|
|
189
194
|
entries,
|
|
190
195
|
});
|
|
191
196
|
},
|
|
192
|
-
[buildEntries],
|
|
197
|
+
[buildEntries, tableContext],
|
|
193
198
|
);
|
|
194
199
|
|
|
195
200
|
// D.5.Nit1 — the return value is consumed directly by the caller
|