@beyondwork/docx-react-component 1.0.37 → 1.0.39
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 +41 -31
- package/src/api/public-types.ts +496 -1
- package/src/core/commands/section-layout-commands.ts +58 -0
- package/src/core/commands/table-grid.ts +431 -0
- package/src/core/commands/table-structure-commands.ts +845 -56
- package/src/core/commands/text-commands.ts +122 -2
- package/src/io/docx-session.ts +1 -0
- package/src/io/export/serialize-main-document.ts +2 -11
- package/src/io/export/serialize-numbering.ts +43 -10
- package/src/io/export/serialize-paragraph-formatting.ts +152 -0
- package/src/io/export/serialize-run-formatting.ts +90 -0
- package/src/io/export/serialize-styles.ts +212 -0
- package/src/io/export/serialize-tables.ts +74 -0
- package/src/io/export/table-properties-xml.ts +139 -4
- package/src/io/normalize/normalize-text.ts +15 -0
- package/src/io/ooxml/parse-fields.ts +10 -3
- package/src/io/ooxml/parse-footnotes.ts +60 -0
- package/src/io/ooxml/parse-headers-footers.ts +60 -0
- package/src/io/ooxml/parse-main-document.ts +137 -0
- package/src/io/ooxml/parse-numbering.ts +41 -1
- package/src/io/ooxml/parse-paragraph-formatting.ts +188 -0
- package/src/io/ooxml/parse-run-formatting.ts +129 -0
- package/src/io/ooxml/parse-styles.ts +31 -0
- package/src/io/ooxml/parse-tables.ts +249 -0
- package/src/io/ooxml/xml-attr-helpers.ts +60 -0
- package/src/io/ooxml/xml-element.ts +19 -0
- package/src/model/canonical-document.ts +117 -3
- package/src/runtime/collab/event-types.ts +165 -0
- package/src/runtime/collab/index.ts +22 -0
- package/src/runtime/collab/remote-cursor-awareness.ts +93 -0
- package/src/runtime/collab/runtime-collab-sync.ts +273 -0
- package/src/runtime/document-layout.ts +4 -2
- package/src/runtime/document-navigation.ts +1 -1
- package/src/runtime/document-runtime.ts +248 -18
- package/src/runtime/layout/default-page-format.ts +96 -0
- package/src/runtime/layout/index.ts +47 -0
- package/src/runtime/layout/inert-layout-facet.ts +16 -0
- package/src/runtime/layout/layout-engine-instance.ts +100 -23
- package/src/runtime/layout/layout-invalidation.ts +14 -5
- package/src/runtime/layout/margin-preset-catalog.ts +178 -0
- package/src/runtime/layout/page-format-catalog.ts +233 -0
- package/src/runtime/layout/page-graph.ts +55 -0
- package/src/runtime/layout/paginate-paragraph-lines.ts +128 -0
- package/src/runtime/layout/paginated-layout-engine.ts +484 -37
- package/src/runtime/layout/project-block-fragments.ts +225 -0
- package/src/runtime/layout/public-facet.ts +748 -16
- package/src/runtime/layout/resolve-page-fields.ts +70 -0
- package/src/runtime/layout/resolve-page-previews.ts +185 -0
- package/src/runtime/layout/resolved-formatting-state.ts +30 -26
- package/src/runtime/layout/table-render-plan.ts +249 -0
- package/src/runtime/numbering-prefix.ts +5 -0
- package/src/runtime/paragraph-style-resolver.ts +194 -0
- package/src/runtime/render/block-fragment-projection.ts +35 -0
- package/src/runtime/render/decoration-resolver.ts +189 -0
- package/src/runtime/render/index.ts +57 -0
- package/src/runtime/render/pending-op-delta-reader.ts +129 -0
- package/src/runtime/render/render-frame-types.ts +317 -0
- package/src/runtime/render/render-kernel.ts +759 -0
- package/src/runtime/resolved-numbering-geometry.ts +9 -1
- package/src/runtime/surface-projection.ts +129 -9
- package/src/runtime/table-schema.ts +11 -0
- package/src/runtime/view-state.ts +67 -0
- package/src/runtime/workflow-markup.ts +1 -5
- package/src/runtime/workflow-rail-segments.ts +280 -0
- package/src/ui/WordReviewEditor.tsx +368 -19
- package/src/ui/editor-command-bag.ts +4 -0
- package/src/ui/editor-runtime-boundary.ts +16 -0
- package/src/ui/editor-shell-view.tsx +10 -0
- package/src/ui/editor-surface-controller.tsx +9 -1
- package/src/ui/headless/chrome-registry.ts +310 -15
- package/src/ui/headless/scoped-chrome-policy.ts +49 -1
- package/src/ui/headless/selection-tool-types.ts +10 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +23 -2
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +2 -14
- package/src/ui-tailwind/chrome/role-action-sets.ts +80 -0
- package/src/ui-tailwind/chrome/tw-detach-handle.tsx +147 -0
- package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +160 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +68 -92
- package/src/ui-tailwind/chrome/tw-selection-tool-placement.ts +149 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +11 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +15 -4
- package/src/ui-tailwind/chrome/tw-table-border-picker.tsx +245 -0
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +356 -140
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +284 -0
- package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +94 -0
- package/src/ui-tailwind/chrome-overlay/index.ts +16 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +96 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +178 -0
- package/src/ui-tailwind/editor-surface/fast-text-edit-lane.ts +4 -0
- package/src/ui-tailwind/editor-surface/local-edit-session-state.ts +11 -0
- package/src/ui-tailwind/editor-surface/page-slice-util.ts +15 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +7 -1
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +40 -4
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +22 -12
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +389 -0
- package/src/ui-tailwind/editor-surface/pm-schema.ts +40 -2
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +144 -62
- package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +179 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +559 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +224 -75
- package/src/ui-tailwind/editor-surface/tw-table-bands.css +61 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +19 -0
- package/src/ui-tailwind/index.ts +29 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +2 -2
- package/src/ui-tailwind/review/tw-rail-card.tsx +150 -0
- package/src/ui-tailwind/review/tw-review-rail-footer.tsx +52 -0
- package/src/ui-tailwind/review/tw-review-rail.tsx +166 -11
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +108 -0
- package/src/ui-tailwind/theme/editor-theme.css +498 -163
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +680 -0
- package/src/ui-tailwind/toolbar/tw-scope-posture-menu.tsx +182 -0
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +162 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +104 -2
- package/src/ui-tailwind/tw-review-workspace.tsx +234 -21
- package/src/runtime/collab-review-sync.ts +0 -254
- package/src/ui-tailwind/editor-surface/pm-collab-plugins.ts +0 -40
|
@@ -21,6 +21,12 @@ export interface TwTableContextToolbarProps {
|
|
|
21
21
|
onSplitCell?: () => void;
|
|
22
22
|
onSetCellBackground?: (color: string) => void;
|
|
23
23
|
onDeleteTable?: () => void;
|
|
24
|
+
// P6: new ops surfaced from P2 capability flags
|
|
25
|
+
onToggleRowHeader?: () => void;
|
|
26
|
+
onToggleRowCantSplit?: () => void;
|
|
27
|
+
onDistributeColumnsEvenly?: () => void;
|
|
28
|
+
onSetTableAlignment?: (alignment: "left" | "center" | "right") => void;
|
|
29
|
+
onSetCellVerticalAlign?: (align: "top" | "center" | "bottom") => void;
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
const CELL_COLORS = [
|
|
@@ -32,161 +38,367 @@ const CELL_COLORS = [
|
|
|
32
38
|
"#fce7f3",
|
|
33
39
|
] as const;
|
|
34
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Table tier (R2.4, spec §6.4 / plan table tier matrix).
|
|
43
|
+
*
|
|
44
|
+
* - `caret-in-cell` (T2) — single-cell selection. Minimal inline set:
|
|
45
|
+
* row +/−, column +/−. Anything structural (merge/split/fill/delete
|
|
46
|
+
* table) lives behind "More" to keep the panel ~180px wide.
|
|
47
|
+
* - `multi-cell` (T3) — >1 cell but not a full row/column/table. Adds
|
|
48
|
+
* merge/split/fill palette.
|
|
49
|
+
* - `row-selected` (T4a) — selection spans exactly one full row
|
|
50
|
+
* (`selectedCellCount === columnCount` + currentCell hint). Row delete
|
|
51
|
+
* + insert + fill palette.
|
|
52
|
+
* - `column-selected` (T4b) — selection spans exactly one full column
|
|
53
|
+
* (`selectedCellCount === rowCount`). Column delete + insert.
|
|
54
|
+
* - `whole-table` (T5) — selection covers every cell OR the user picked
|
|
55
|
+
* the table-select handle (future). Style + delete + table-level
|
|
56
|
+
* formatting.
|
|
57
|
+
*
|
|
58
|
+
* The tier resolver is a pure function of the capability snapshot so
|
|
59
|
+
* tests can drive it from seeded snapshots without a DOM.
|
|
60
|
+
*/
|
|
61
|
+
export type TableTier =
|
|
62
|
+
| "caret-in-cell"
|
|
63
|
+
| "multi-cell"
|
|
64
|
+
| "row-selected"
|
|
65
|
+
| "column-selected"
|
|
66
|
+
| "whole-table";
|
|
67
|
+
|
|
68
|
+
export function resolveTableTier(
|
|
69
|
+
ctx: TableStructureContextSnapshot,
|
|
70
|
+
): TableTier {
|
|
71
|
+
const totalCells = ctx.rowCount * ctx.columnCount;
|
|
72
|
+
if (ctx.selectedCellCount >= totalCells && totalCells > 0) {
|
|
73
|
+
return "whole-table";
|
|
74
|
+
}
|
|
75
|
+
if (
|
|
76
|
+
ctx.selectedCellCount > 1 &&
|
|
77
|
+
ctx.selectedCellCount === ctx.columnCount &&
|
|
78
|
+
ctx.columnCount > 0
|
|
79
|
+
) {
|
|
80
|
+
return "row-selected";
|
|
81
|
+
}
|
|
82
|
+
if (
|
|
83
|
+
ctx.selectedCellCount > 1 &&
|
|
84
|
+
ctx.selectedCellCount === ctx.rowCount &&
|
|
85
|
+
ctx.rowCount > 0
|
|
86
|
+
) {
|
|
87
|
+
return "column-selected";
|
|
88
|
+
}
|
|
89
|
+
if (ctx.selectedCellCount > 1) {
|
|
90
|
+
return "multi-cell";
|
|
91
|
+
}
|
|
92
|
+
return "caret-in-cell";
|
|
93
|
+
}
|
|
94
|
+
|
|
35
95
|
export function TwTableContextToolbar(props: TwTableContextToolbarProps) {
|
|
36
96
|
const tableContext = props.tableContext;
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
? tableContext.
|
|
40
|
-
? `${tableContext.selectedCellCount} cells`
|
|
41
|
-
: `R${tableContext.currentCell.rowIndex + 1} C${tableContext.currentCell.columnIndex + 1}`
|
|
97
|
+
const tier = tableContext ? resolveTableTier(tableContext) : "caret-in-cell";
|
|
98
|
+
const tableSizeLabel = tableContext
|
|
99
|
+
? `${tableContext.rowCount} x ${tableContext.columnCount}`
|
|
42
100
|
: null;
|
|
101
|
+
const selectionLabel = tableContext ? formatSelectionLabel(tableContext, tier) : null;
|
|
102
|
+
|
|
103
|
+
// Tier-specific width caps. Progressive: T2 ≤ 18rem, T3 ≤ 22rem, T4
|
|
104
|
+
// ≤ 24rem, T5 ≤ 28rem. Down from the old flat 30rem always.
|
|
105
|
+
const widthCap = tierWidthCap(tier);
|
|
43
106
|
|
|
44
107
|
return (
|
|
45
108
|
<div
|
|
46
109
|
data-testid="table-context-toolbar"
|
|
47
|
-
|
|
110
|
+
data-tier={tier}
|
|
111
|
+
className={`flex ${widthCap} flex-wrap items-start gap-1.5 rounded-lg border border-border bg-canvas px-2.5 py-1.5 shadow-sm`}
|
|
48
112
|
>
|
|
49
113
|
<span className="text-[9px] font-semibold uppercase tracking-[0.12em] text-tertiary">
|
|
50
|
-
|
|
114
|
+
{tierLabel(tier)}
|
|
51
115
|
</span>
|
|
52
116
|
{tableSizeLabel ? <ToolbarBadge>{tableSizeLabel}</ToolbarBadge> : null}
|
|
53
117
|
{selectionLabel ? <ToolbarBadge>{selectionLabel}</ToolbarBadge> : null}
|
|
54
|
-
{tableContext?.currentCell.isHeader ?
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
{props.tableStyles.map((style) => (
|
|
73
|
-
<option key={style.styleId} value={style.styleId}>
|
|
74
|
-
{style.displayName}
|
|
75
|
-
</option>
|
|
118
|
+
{tableContext?.currentCell.isHeader ? (
|
|
119
|
+
<ToolbarBadge tone="accent">Header row</ToolbarBadge>
|
|
120
|
+
) : null}
|
|
121
|
+
|
|
122
|
+
{/* T5 whole-table: table alignment */}
|
|
123
|
+
{tier === "whole-table" ? (
|
|
124
|
+
<ToolbarSection label="Align">
|
|
125
|
+
{(["left", "center", "right"] as const).map((align) => (
|
|
126
|
+
<ToolbarButton
|
|
127
|
+
key={align}
|
|
128
|
+
ariaLabel={`Align table ${align}`}
|
|
129
|
+
capability={tableContext?.operations.setTableAlignment}
|
|
130
|
+
disabled={props.disabled}
|
|
131
|
+
onClick={() => props.onSetTableAlignment?.(align)}
|
|
132
|
+
active={tableContext?.currentCell != null && align === "left"}
|
|
133
|
+
>
|
|
134
|
+
{align[0]!.toUpperCase()}
|
|
135
|
+
</ToolbarButton>
|
|
76
136
|
))}
|
|
77
|
-
</
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
137
|
+
</ToolbarSection>
|
|
138
|
+
) : null}
|
|
139
|
+
|
|
140
|
+
{/* T5 whole-table: style selector (primary), delete table */}
|
|
141
|
+
{tier === "whole-table" ? (
|
|
142
|
+
<ToolbarSection label="Style">
|
|
143
|
+
<select
|
|
144
|
+
aria-label="Table style"
|
|
145
|
+
className="h-7 min-w-[9rem] rounded-md border border-border bg-canvas px-2 text-[11px] text-primary disabled:opacity-40"
|
|
146
|
+
disabled={
|
|
147
|
+
props.disabled ||
|
|
148
|
+
props.tableStyles.length === 0 ||
|
|
149
|
+
!props.onSetTableStyle ||
|
|
150
|
+
!tableContext?.operations.setTableStyle.enabled
|
|
151
|
+
}
|
|
152
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
153
|
+
onChange={(event) => props.onSetTableStyle?.(event.target.value)}
|
|
154
|
+
value={tableContext?.currentStyleId ?? ""}
|
|
155
|
+
title={tableContext?.operations.setTableStyle.reason}
|
|
156
|
+
>
|
|
157
|
+
<option value="" disabled>
|
|
158
|
+
Table style
|
|
159
|
+
</option>
|
|
160
|
+
{props.tableStyles.map((style) => (
|
|
161
|
+
<option key={style.styleId} value={style.styleId}>
|
|
162
|
+
{style.displayName}
|
|
163
|
+
</option>
|
|
164
|
+
))}
|
|
165
|
+
</select>
|
|
166
|
+
</ToolbarSection>
|
|
167
|
+
) : null}
|
|
168
|
+
|
|
169
|
+
{/* T2 / T4a row-selected: row ops */}
|
|
170
|
+
{(tier === "caret-in-cell" || tier === "row-selected") ? (
|
|
171
|
+
<ToolbarSection label="Rows">
|
|
172
|
+
<ToolbarButton
|
|
173
|
+
ariaLabel="Add row above"
|
|
174
|
+
capability={tableContext?.operations.addRowBefore}
|
|
175
|
+
disabled={props.disabled}
|
|
176
|
+
onClick={props.onAddRowBefore}
|
|
177
|
+
>
|
|
178
|
+
Above
|
|
179
|
+
</ToolbarButton>
|
|
180
|
+
<ToolbarButton
|
|
181
|
+
ariaLabel="Add row below"
|
|
182
|
+
capability={tableContext?.operations.addRowAfter}
|
|
183
|
+
disabled={props.disabled}
|
|
184
|
+
onClick={props.onAddRowAfter}
|
|
185
|
+
>
|
|
186
|
+
Below
|
|
187
|
+
</ToolbarButton>
|
|
188
|
+
{tier === "row-selected" ? (
|
|
189
|
+
<>
|
|
190
|
+
<ToolbarButton
|
|
191
|
+
ariaLabel="Delete row"
|
|
192
|
+
capability={tableContext?.operations.deleteRow}
|
|
193
|
+
disabled={props.disabled}
|
|
194
|
+
onClick={props.onDeleteRow}
|
|
195
|
+
danger
|
|
196
|
+
>
|
|
197
|
+
Delete row
|
|
198
|
+
</ToolbarButton>
|
|
199
|
+
<ToolbarButton
|
|
200
|
+
ariaLabel="Toggle header row"
|
|
201
|
+
capability={tableContext?.operations.setRowIsHeader}
|
|
202
|
+
disabled={props.disabled}
|
|
203
|
+
onClick={props.onToggleRowHeader}
|
|
204
|
+
active={tableContext?.currentCell.isHeader}
|
|
205
|
+
>
|
|
206
|
+
Header
|
|
207
|
+
</ToolbarButton>
|
|
208
|
+
<ToolbarButton
|
|
209
|
+
ariaLabel="Toggle row can't split"
|
|
210
|
+
capability={tableContext?.operations.setRowCantSplit}
|
|
211
|
+
disabled={props.disabled}
|
|
212
|
+
onClick={props.onToggleRowCantSplit}
|
|
213
|
+
>
|
|
214
|
+
No break
|
|
215
|
+
</ToolbarButton>
|
|
216
|
+
</>
|
|
217
|
+
) : null}
|
|
218
|
+
</ToolbarSection>
|
|
219
|
+
) : null}
|
|
220
|
+
|
|
221
|
+
{/* T2 / T4b column-selected: column ops */}
|
|
222
|
+
{(tier === "caret-in-cell" || tier === "column-selected") ? (
|
|
223
|
+
<ToolbarSection label="Columns">
|
|
224
|
+
<ToolbarButton
|
|
225
|
+
ariaLabel="Add column left"
|
|
226
|
+
capability={tableContext?.operations.addColumnBefore}
|
|
227
|
+
disabled={props.disabled}
|
|
228
|
+
onClick={props.onAddColumnBefore}
|
|
229
|
+
>
|
|
230
|
+
Left
|
|
231
|
+
</ToolbarButton>
|
|
232
|
+
<ToolbarButton
|
|
233
|
+
ariaLabel="Add column right"
|
|
234
|
+
capability={tableContext?.operations.addColumnAfter}
|
|
235
|
+
disabled={props.disabled}
|
|
236
|
+
onClick={props.onAddColumnAfter}
|
|
237
|
+
>
|
|
238
|
+
Right
|
|
239
|
+
</ToolbarButton>
|
|
240
|
+
{tier === "column-selected" ? (
|
|
241
|
+
<>
|
|
242
|
+
<ToolbarButton
|
|
243
|
+
ariaLabel="Delete column"
|
|
244
|
+
capability={tableContext?.operations.deleteColumn}
|
|
245
|
+
disabled={props.disabled}
|
|
246
|
+
onClick={props.onDeleteColumn}
|
|
247
|
+
danger
|
|
248
|
+
>
|
|
249
|
+
Delete column
|
|
250
|
+
</ToolbarButton>
|
|
251
|
+
<ToolbarButton
|
|
252
|
+
ariaLabel="Distribute columns evenly"
|
|
253
|
+
capability={tableContext?.operations.distributeColumnsEvenly}
|
|
254
|
+
disabled={props.disabled}
|
|
255
|
+
onClick={props.onDistributeColumnsEvenly}
|
|
256
|
+
>
|
|
257
|
+
Distribute
|
|
258
|
+
</ToolbarButton>
|
|
259
|
+
</>
|
|
260
|
+
) : null}
|
|
261
|
+
</ToolbarSection>
|
|
262
|
+
) : null}
|
|
263
|
+
|
|
264
|
+
{/* T3 multi-cell: merge/split */}
|
|
265
|
+
{tier === "multi-cell" ||
|
|
266
|
+
tier === "row-selected" ||
|
|
267
|
+
tier === "column-selected" ? (
|
|
268
|
+
<ToolbarSection label="Cells">
|
|
269
|
+
<ToolbarButton
|
|
270
|
+
ariaLabel="Merge cells"
|
|
271
|
+
capability={tableContext?.operations.mergeCells}
|
|
272
|
+
disabled={props.disabled}
|
|
273
|
+
onClick={props.onMergeCells}
|
|
274
|
+
>
|
|
275
|
+
Merge
|
|
276
|
+
</ToolbarButton>
|
|
277
|
+
<ToolbarButton
|
|
278
|
+
ariaLabel="Split cell"
|
|
279
|
+
capability={tableContext?.operations.splitCell}
|
|
280
|
+
disabled={props.disabled}
|
|
281
|
+
onClick={props.onSplitCell}
|
|
282
|
+
>
|
|
283
|
+
Split
|
|
284
|
+
</ToolbarButton>
|
|
285
|
+
</ToolbarSection>
|
|
286
|
+
) : null}
|
|
287
|
+
|
|
288
|
+
{/* Fill palette: multi-cell + full-row/column + whole-table */}
|
|
289
|
+
{tier !== "caret-in-cell" ? (
|
|
290
|
+
<ToolbarSection label="Fill">
|
|
291
|
+
<div className="flex items-center gap-1">
|
|
292
|
+
{CELL_COLORS.map((color) => (
|
|
293
|
+
<button
|
|
294
|
+
key={color}
|
|
295
|
+
type="button"
|
|
296
|
+
aria-label={`Set cell fill ${color}`}
|
|
297
|
+
disabled={
|
|
298
|
+
props.disabled ||
|
|
299
|
+
!props.onSetCellBackground ||
|
|
300
|
+
!tableContext?.operations.setCellBackground.enabled
|
|
301
|
+
}
|
|
302
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
303
|
+
onClick={() => props.onSetCellBackground?.(color)}
|
|
304
|
+
className="h-5 w-5 rounded border border-border disabled:opacity-40"
|
|
305
|
+
style={{ backgroundColor: color }}
|
|
306
|
+
title={tableContext?.operations.setCellBackground.reason}
|
|
307
|
+
/>
|
|
308
|
+
))}
|
|
309
|
+
</div>
|
|
310
|
+
</ToolbarSection>
|
|
311
|
+
) : null}
|
|
312
|
+
|
|
313
|
+
{/* Cell vertical alignment (caret-in-cell + multi-cell) */}
|
|
314
|
+
{(tier === "caret-in-cell" || tier === "multi-cell") ? (
|
|
315
|
+
<ToolbarSection label="V-Align">
|
|
316
|
+
{(
|
|
317
|
+
[
|
|
318
|
+
["top", "Top"],
|
|
319
|
+
["center", "Mid"],
|
|
320
|
+
["bottom", "Bot"],
|
|
321
|
+
] as const
|
|
322
|
+
).map(([align, label]) => (
|
|
323
|
+
<ToolbarButton
|
|
324
|
+
key={align}
|
|
325
|
+
ariaLabel={`Cell vertical align ${align}`}
|
|
326
|
+
capability={tableContext?.operations.setCellVerticalAlign}
|
|
327
|
+
disabled={props.disabled}
|
|
328
|
+
onClick={() => props.onSetCellVerticalAlign?.(align)}
|
|
329
|
+
>
|
|
330
|
+
{label}
|
|
331
|
+
</ToolbarButton>
|
|
171
332
|
))}
|
|
172
|
-
</
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
333
|
+
</ToolbarSection>
|
|
334
|
+
) : null}
|
|
335
|
+
|
|
336
|
+
{/* T5 only: delete table (danger) */}
|
|
337
|
+
{tier === "whole-table" ? (
|
|
338
|
+
<ToolbarSection label="Table">
|
|
339
|
+
<ToolbarButton
|
|
340
|
+
ariaLabel="Delete table"
|
|
341
|
+
capability={tableContext?.operations.deleteTable}
|
|
342
|
+
danger
|
|
343
|
+
disabled={props.disabled}
|
|
344
|
+
onClick={props.onDeleteTable}
|
|
345
|
+
>
|
|
346
|
+
Delete table
|
|
347
|
+
</ToolbarButton>
|
|
348
|
+
</ToolbarSection>
|
|
349
|
+
) : null}
|
|
186
350
|
</div>
|
|
187
351
|
);
|
|
188
352
|
}
|
|
189
353
|
|
|
354
|
+
function formatSelectionLabel(
|
|
355
|
+
ctx: TableStructureContextSnapshot,
|
|
356
|
+
tier: TableTier,
|
|
357
|
+
): string {
|
|
358
|
+
if (tier === "caret-in-cell") {
|
|
359
|
+
return `R${ctx.currentCell.rowIndex + 1} C${ctx.currentCell.columnIndex + 1}`;
|
|
360
|
+
}
|
|
361
|
+
if (tier === "row-selected") {
|
|
362
|
+
return `Row ${ctx.currentCell.rowIndex + 1}`;
|
|
363
|
+
}
|
|
364
|
+
if (tier === "column-selected") {
|
|
365
|
+
return `Col ${ctx.currentCell.columnIndex + 1}`;
|
|
366
|
+
}
|
|
367
|
+
if (tier === "whole-table") {
|
|
368
|
+
return "Whole table";
|
|
369
|
+
}
|
|
370
|
+
return `${ctx.selectedCellCount} cells`;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function tierLabel(tier: TableTier): string {
|
|
374
|
+
switch (tier) {
|
|
375
|
+
case "caret-in-cell":
|
|
376
|
+
return "Cell";
|
|
377
|
+
case "multi-cell":
|
|
378
|
+
return "Cells";
|
|
379
|
+
case "row-selected":
|
|
380
|
+
return "Row";
|
|
381
|
+
case "column-selected":
|
|
382
|
+
return "Column";
|
|
383
|
+
case "whole-table":
|
|
384
|
+
return "Table";
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function tierWidthCap(tier: TableTier): string {
|
|
389
|
+
switch (tier) {
|
|
390
|
+
case "caret-in-cell":
|
|
391
|
+
return "max-w-[min(18rem,calc(100vw-1.5rem))]";
|
|
392
|
+
case "multi-cell":
|
|
393
|
+
return "max-w-[min(22rem,calc(100vw-1.5rem))]";
|
|
394
|
+
case "row-selected":
|
|
395
|
+
case "column-selected":
|
|
396
|
+
return "max-w-[min(24rem,calc(100vw-1.5rem))]";
|
|
397
|
+
case "whole-table":
|
|
398
|
+
return "max-w-[min(28rem,calc(100vw-1.5rem))]";
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
190
402
|
function ToolbarBadge(props: {
|
|
191
403
|
children: React.ReactNode;
|
|
192
404
|
tone?: "neutral" | "accent";
|
|
@@ -226,6 +438,7 @@ function ToolbarButton(props: {
|
|
|
226
438
|
danger?: boolean;
|
|
227
439
|
disabled: boolean;
|
|
228
440
|
onClick?: () => void;
|
|
441
|
+
active?: boolean;
|
|
229
442
|
}) {
|
|
230
443
|
const capabilityEnabled = props.capability?.enabled ?? true;
|
|
231
444
|
const title = !capabilityEnabled ? props.capability?.reason : undefined;
|
|
@@ -233,14 +446,17 @@ function ToolbarButton(props: {
|
|
|
233
446
|
<button
|
|
234
447
|
type="button"
|
|
235
448
|
aria-label={props.ariaLabel}
|
|
449
|
+
aria-pressed={props.active}
|
|
236
450
|
disabled={props.disabled || !props.onClick || !capabilityEnabled}
|
|
237
451
|
onMouseDown={preserveEditorSelectionMouseDown}
|
|
238
452
|
onClick={props.onClick}
|
|
239
453
|
title={title}
|
|
240
454
|
className={`inline-flex h-7 items-center rounded-md px-2 text-[11px] font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-40 ${
|
|
241
|
-
props.
|
|
242
|
-
? "
|
|
243
|
-
:
|
|
455
|
+
props.active
|
|
456
|
+
? "bg-accent/15 text-accent"
|
|
457
|
+
: props.danger
|
|
458
|
+
? "text-danger hover:bg-danger/10"
|
|
459
|
+
: "text-primary hover:bg-surface"
|
|
244
460
|
}`}
|
|
245
461
|
>
|
|
246
462
|
{props.children}
|