@beyondwork/docx-react-component 1.0.37 → 1.0.38
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 +319 -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 +815 -55
- package/src/io/export/serialize-main-document.ts +2 -11
- package/src/io/export/serialize-numbering.ts +1 -2
- 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-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-tables.ts +249 -0
- package/src/model/canonical-document.ts +34 -0
- package/src/runtime/document-layout.ts +4 -2
- package/src/runtime/document-navigation.ts +1 -1
- package/src/runtime/document-runtime.ts +114 -0
- package/src/runtime/layout/default-page-format.ts +96 -0
- package/src/runtime/layout/index.ts +45 -0
- package/src/runtime/layout/inert-layout-facet.ts +14 -0
- package/src/runtime/layout/layout-engine-instance.ts +33 -23
- 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 +19 -0
- package/src/runtime/layout/paginated-layout-engine.ts +142 -9
- package/src/runtime/layout/project-block-fragments.ts +91 -0
- package/src/runtime/layout/public-facet.ts +709 -16
- package/src/runtime/layout/table-render-plan.ts +229 -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 +755 -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 +84 -15
- package/src/ui/editor-shell-view.tsx +6 -0
- package/src/ui/headless/chrome-registry.ts +280 -14
- package/src/ui/headless/scoped-chrome-policy.ts +20 -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/role-action-sets.ts +74 -0
- package/src/ui-tailwind/chrome/tw-detach-handle.tsx +147 -0
- package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +163 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +57 -92
- package/src/ui-tailwind/chrome/tw-selection-tool-placement.ts +149 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +15 -4
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +274 -138
- package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +90 -0
- package/src/ui-tailwind/chrome-overlay/index.ts +22 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +86 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +178 -0
- package/src/ui-tailwind/chrome-overlay/tw-workspace-view-switcher.tsx +95 -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/perf-probe.ts +7 -1
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +12 -1
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +22 -12
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +3 -0
- package/src/ui-tailwind/index.ts +33 -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 +559 -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 +69 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +136 -1
|
@@ -22,6 +22,15 @@ export interface TwSelectionToolbarProps {
|
|
|
22
22
|
const focusRingClass =
|
|
23
23
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-canvas";
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Fallback colors for the selection-toolbar one-click apply buttons.
|
|
27
|
+
* The model can override via `textColorDefault` / `highlightColorDefault`
|
|
28
|
+
* which R2.5 plumbs through from `formattingState` so the apply button
|
|
29
|
+
* reflects the user's recent color pick.
|
|
30
|
+
*/
|
|
31
|
+
const DEFAULT_TEXT_COLOR = "#1660a8";
|
|
32
|
+
const DEFAULT_HIGHLIGHT_COLOR = "#ffff00";
|
|
33
|
+
|
|
25
34
|
export const TwSelectionToolbar = forwardRef<HTMLDivElement, TwSelectionToolbarProps>(function TwSelectionToolbar(props, ref) {
|
|
26
35
|
const { model } = props;
|
|
27
36
|
const addCommentDisabled = !model.canAddComment;
|
|
@@ -64,17 +73,19 @@ export const TwSelectionToolbar = forwardRef<HTMLDivElement, TwSelectionToolbarP
|
|
|
64
73
|
/>
|
|
65
74
|
<ToolbarActionButton
|
|
66
75
|
icon={<Baseline className="h-3.5 w-3.5" />}
|
|
67
|
-
label=
|
|
76
|
+
label={`Apply ${model.textColorDefault ?? DEFAULT_TEXT_COLOR}`}
|
|
68
77
|
pressed={false}
|
|
69
78
|
disabled={formattingDisabled}
|
|
70
|
-
onClick={() => props.onSetTextColor?.(
|
|
79
|
+
onClick={() => props.onSetTextColor?.(model.textColorDefault ?? DEFAULT_TEXT_COLOR)}
|
|
71
80
|
/>
|
|
72
81
|
<ToolbarActionButton
|
|
73
82
|
icon={<Highlighter className="h-3.5 w-3.5" />}
|
|
74
|
-
label=
|
|
83
|
+
label={`Apply ${model.highlightColorDefault ?? DEFAULT_HIGHLIGHT_COLOR} highlight`}
|
|
75
84
|
pressed={false}
|
|
76
85
|
disabled={formattingDisabled}
|
|
77
|
-
onClick={() =>
|
|
86
|
+
onClick={() =>
|
|
87
|
+
props.onSetHighlightColor?.(model.highlightColorDefault ?? DEFAULT_HIGHLIGHT_COLOR)
|
|
88
|
+
}
|
|
78
89
|
/>
|
|
79
90
|
|
|
80
91
|
<div className="mx-0.5 h-4 w-px bg-border" />
|
|
@@ -32,161 +32,297 @@ const CELL_COLORS = [
|
|
|
32
32
|
"#fce7f3",
|
|
33
33
|
] as const;
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Table tier (R2.4, spec §6.4 / plan table tier matrix).
|
|
37
|
+
*
|
|
38
|
+
* - `caret-in-cell` (T2) — single-cell selection. Minimal inline set:
|
|
39
|
+
* row +/−, column +/−. Anything structural (merge/split/fill/delete
|
|
40
|
+
* table) lives behind "More" to keep the panel ~180px wide.
|
|
41
|
+
* - `multi-cell` (T3) — >1 cell but not a full row/column/table. Adds
|
|
42
|
+
* merge/split/fill palette.
|
|
43
|
+
* - `row-selected` (T4a) — selection spans exactly one full row
|
|
44
|
+
* (`selectedCellCount === columnCount` + currentCell hint). Row delete
|
|
45
|
+
* + insert + fill palette.
|
|
46
|
+
* - `column-selected` (T4b) — selection spans exactly one full column
|
|
47
|
+
* (`selectedCellCount === rowCount`). Column delete + insert.
|
|
48
|
+
* - `whole-table` (T5) — selection covers every cell OR the user picked
|
|
49
|
+
* the table-select handle (future). Style + delete + table-level
|
|
50
|
+
* formatting.
|
|
51
|
+
*
|
|
52
|
+
* The tier resolver is a pure function of the capability snapshot so
|
|
53
|
+
* tests can drive it from seeded snapshots without a DOM.
|
|
54
|
+
*/
|
|
55
|
+
export type TableTier =
|
|
56
|
+
| "caret-in-cell"
|
|
57
|
+
| "multi-cell"
|
|
58
|
+
| "row-selected"
|
|
59
|
+
| "column-selected"
|
|
60
|
+
| "whole-table";
|
|
61
|
+
|
|
62
|
+
export function resolveTableTier(
|
|
63
|
+
ctx: TableStructureContextSnapshot,
|
|
64
|
+
): TableTier {
|
|
65
|
+
const totalCells = ctx.rowCount * ctx.columnCount;
|
|
66
|
+
if (ctx.selectedCellCount >= totalCells && totalCells > 0) {
|
|
67
|
+
return "whole-table";
|
|
68
|
+
}
|
|
69
|
+
if (
|
|
70
|
+
ctx.selectedCellCount > 1 &&
|
|
71
|
+
ctx.selectedCellCount === ctx.columnCount &&
|
|
72
|
+
ctx.columnCount > 0
|
|
73
|
+
) {
|
|
74
|
+
return "row-selected";
|
|
75
|
+
}
|
|
76
|
+
if (
|
|
77
|
+
ctx.selectedCellCount > 1 &&
|
|
78
|
+
ctx.selectedCellCount === ctx.rowCount &&
|
|
79
|
+
ctx.rowCount > 0
|
|
80
|
+
) {
|
|
81
|
+
return "column-selected";
|
|
82
|
+
}
|
|
83
|
+
if (ctx.selectedCellCount > 1) {
|
|
84
|
+
return "multi-cell";
|
|
85
|
+
}
|
|
86
|
+
return "caret-in-cell";
|
|
87
|
+
}
|
|
88
|
+
|
|
35
89
|
export function TwTableContextToolbar(props: TwTableContextToolbarProps) {
|
|
36
90
|
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}`
|
|
91
|
+
const tier = tableContext ? resolveTableTier(tableContext) : "caret-in-cell";
|
|
92
|
+
const tableSizeLabel = tableContext
|
|
93
|
+
? `${tableContext.rowCount} x ${tableContext.columnCount}`
|
|
42
94
|
: null;
|
|
95
|
+
const selectionLabel = tableContext ? formatSelectionLabel(tableContext, tier) : null;
|
|
96
|
+
|
|
97
|
+
// Tier-specific width caps. Progressive: T2 ≤ 18rem, T3 ≤ 22rem, T4
|
|
98
|
+
// ≤ 24rem, T5 ≤ 28rem. Down from the old flat 30rem always.
|
|
99
|
+
const widthCap = tierWidthCap(tier);
|
|
43
100
|
|
|
44
101
|
return (
|
|
45
102
|
<div
|
|
46
103
|
data-testid="table-context-toolbar"
|
|
47
|
-
|
|
104
|
+
data-tier={tier}
|
|
105
|
+
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
106
|
>
|
|
49
107
|
<span className="text-[9px] font-semibold uppercase tracking-[0.12em] text-tertiary">
|
|
50
|
-
|
|
108
|
+
{tierLabel(tier)}
|
|
51
109
|
</span>
|
|
52
110
|
{tableSizeLabel ? <ToolbarBadge>{tableSizeLabel}</ToolbarBadge> : null}
|
|
53
111
|
{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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
112
|
+
{tableContext?.currentCell.isHeader ? (
|
|
113
|
+
<ToolbarBadge tone="accent">Header row</ToolbarBadge>
|
|
114
|
+
) : null}
|
|
115
|
+
|
|
116
|
+
{/* T5 whole-table: style selector (primary), delete table */}
|
|
117
|
+
{tier === "whole-table" ? (
|
|
118
|
+
<ToolbarSection label="Style">
|
|
119
|
+
<select
|
|
120
|
+
aria-label="Table style"
|
|
121
|
+
className="h-7 min-w-[9rem] rounded-md border border-border bg-canvas px-2 text-[11px] text-primary disabled:opacity-40"
|
|
122
|
+
disabled={
|
|
123
|
+
props.disabled ||
|
|
124
|
+
props.tableStyles.length === 0 ||
|
|
125
|
+
!props.onSetTableStyle ||
|
|
126
|
+
!tableContext?.operations.setTableStyle.enabled
|
|
127
|
+
}
|
|
128
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
129
|
+
onChange={(event) => props.onSetTableStyle?.(event.target.value)}
|
|
130
|
+
value={tableContext?.currentStyleId ?? ""}
|
|
131
|
+
title={tableContext?.operations.setTableStyle.reason}
|
|
132
|
+
>
|
|
133
|
+
<option value="" disabled>
|
|
134
|
+
Table style
|
|
75
135
|
</option>
|
|
76
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
</
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
136
|
+
{props.tableStyles.map((style) => (
|
|
137
|
+
<option key={style.styleId} value={style.styleId}>
|
|
138
|
+
{style.displayName}
|
|
139
|
+
</option>
|
|
140
|
+
))}
|
|
141
|
+
</select>
|
|
142
|
+
</ToolbarSection>
|
|
143
|
+
) : null}
|
|
144
|
+
|
|
145
|
+
{/* T2 / T4a row-selected: row ops */}
|
|
146
|
+
{(tier === "caret-in-cell" || tier === "row-selected") ? (
|
|
147
|
+
<ToolbarSection label="Rows">
|
|
148
|
+
<ToolbarButton
|
|
149
|
+
ariaLabel="Add row above"
|
|
150
|
+
capability={tableContext?.operations.addRowBefore}
|
|
151
|
+
disabled={props.disabled}
|
|
152
|
+
onClick={props.onAddRowBefore}
|
|
153
|
+
>
|
|
154
|
+
Above
|
|
155
|
+
</ToolbarButton>
|
|
156
|
+
<ToolbarButton
|
|
157
|
+
ariaLabel="Add row below"
|
|
158
|
+
capability={tableContext?.operations.addRowAfter}
|
|
159
|
+
disabled={props.disabled}
|
|
160
|
+
onClick={props.onAddRowAfter}
|
|
161
|
+
>
|
|
162
|
+
Below
|
|
163
|
+
</ToolbarButton>
|
|
164
|
+
{tier === "row-selected" ? (
|
|
165
|
+
<ToolbarButton
|
|
166
|
+
ariaLabel="Delete row"
|
|
167
|
+
capability={tableContext?.operations.deleteRow}
|
|
168
|
+
disabled={props.disabled}
|
|
169
|
+
onClick={props.onDeleteRow}
|
|
170
|
+
danger
|
|
171
|
+
>
|
|
172
|
+
Delete row
|
|
173
|
+
</ToolbarButton>
|
|
174
|
+
) : null}
|
|
175
|
+
</ToolbarSection>
|
|
176
|
+
) : null}
|
|
177
|
+
|
|
178
|
+
{/* T2 / T4b column-selected: column ops */}
|
|
179
|
+
{(tier === "caret-in-cell" || tier === "column-selected") ? (
|
|
180
|
+
<ToolbarSection label="Columns">
|
|
181
|
+
<ToolbarButton
|
|
182
|
+
ariaLabel="Add column left"
|
|
183
|
+
capability={tableContext?.operations.addColumnBefore}
|
|
184
|
+
disabled={props.disabled}
|
|
185
|
+
onClick={props.onAddColumnBefore}
|
|
186
|
+
>
|
|
187
|
+
Left
|
|
188
|
+
</ToolbarButton>
|
|
189
|
+
<ToolbarButton
|
|
190
|
+
ariaLabel="Add column right"
|
|
191
|
+
capability={tableContext?.operations.addColumnAfter}
|
|
192
|
+
disabled={props.disabled}
|
|
193
|
+
onClick={props.onAddColumnAfter}
|
|
194
|
+
>
|
|
195
|
+
Right
|
|
196
|
+
</ToolbarButton>
|
|
197
|
+
{tier === "column-selected" ? (
|
|
198
|
+
<ToolbarButton
|
|
199
|
+
ariaLabel="Delete column"
|
|
200
|
+
capability={tableContext?.operations.deleteColumn}
|
|
201
|
+
disabled={props.disabled}
|
|
202
|
+
onClick={props.onDeleteColumn}
|
|
203
|
+
danger
|
|
204
|
+
>
|
|
205
|
+
Delete column
|
|
206
|
+
</ToolbarButton>
|
|
207
|
+
) : null}
|
|
208
|
+
</ToolbarSection>
|
|
209
|
+
) : null}
|
|
210
|
+
|
|
211
|
+
{/* T3 multi-cell: merge/split */}
|
|
212
|
+
{tier === "multi-cell" ||
|
|
213
|
+
tier === "row-selected" ||
|
|
214
|
+
tier === "column-selected" ? (
|
|
215
|
+
<ToolbarSection label="Cells">
|
|
216
|
+
<ToolbarButton
|
|
217
|
+
ariaLabel="Merge cells"
|
|
218
|
+
capability={tableContext?.operations.mergeCells}
|
|
219
|
+
disabled={props.disabled}
|
|
220
|
+
onClick={props.onMergeCells}
|
|
221
|
+
>
|
|
222
|
+
Merge
|
|
223
|
+
</ToolbarButton>
|
|
224
|
+
<ToolbarButton
|
|
225
|
+
ariaLabel="Split cell"
|
|
226
|
+
capability={tableContext?.operations.splitCell}
|
|
227
|
+
disabled={props.disabled}
|
|
228
|
+
onClick={props.onSplitCell}
|
|
229
|
+
>
|
|
230
|
+
Split
|
|
231
|
+
</ToolbarButton>
|
|
232
|
+
</ToolbarSection>
|
|
233
|
+
) : null}
|
|
234
|
+
|
|
235
|
+
{/* Fill palette: multi-cell + full-row/column + whole-table */}
|
|
236
|
+
{tier !== "caret-in-cell" ? (
|
|
237
|
+
<ToolbarSection label="Fill">
|
|
238
|
+
<div className="flex items-center gap-1">
|
|
239
|
+
{CELL_COLORS.map((color) => (
|
|
240
|
+
<button
|
|
241
|
+
key={color}
|
|
242
|
+
type="button"
|
|
243
|
+
aria-label={`Set cell fill ${color}`}
|
|
244
|
+
disabled={
|
|
245
|
+
props.disabled ||
|
|
246
|
+
!props.onSetCellBackground ||
|
|
247
|
+
!tableContext?.operations.setCellBackground.enabled
|
|
248
|
+
}
|
|
249
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
250
|
+
onClick={() => props.onSetCellBackground?.(color)}
|
|
251
|
+
className="h-5 w-5 rounded border border-border disabled:opacity-40"
|
|
252
|
+
style={{ backgroundColor: color }}
|
|
253
|
+
title={tableContext?.operations.setCellBackground.reason}
|
|
254
|
+
/>
|
|
255
|
+
))}
|
|
256
|
+
</div>
|
|
257
|
+
</ToolbarSection>
|
|
258
|
+
) : null}
|
|
259
|
+
|
|
260
|
+
{/* T5 only: delete table (danger) */}
|
|
261
|
+
{tier === "whole-table" ? (
|
|
262
|
+
<ToolbarSection label="Table">
|
|
263
|
+
<ToolbarButton
|
|
264
|
+
ariaLabel="Delete table"
|
|
265
|
+
capability={tableContext?.operations.deleteTable}
|
|
266
|
+
danger
|
|
267
|
+
disabled={props.disabled}
|
|
268
|
+
onClick={props.onDeleteTable}
|
|
269
|
+
>
|
|
270
|
+
Delete table
|
|
271
|
+
</ToolbarButton>
|
|
272
|
+
</ToolbarSection>
|
|
273
|
+
) : null}
|
|
186
274
|
</div>
|
|
187
275
|
);
|
|
188
276
|
}
|
|
189
277
|
|
|
278
|
+
function formatSelectionLabel(
|
|
279
|
+
ctx: TableStructureContextSnapshot,
|
|
280
|
+
tier: TableTier,
|
|
281
|
+
): string {
|
|
282
|
+
if (tier === "caret-in-cell") {
|
|
283
|
+
return `R${ctx.currentCell.rowIndex + 1} C${ctx.currentCell.columnIndex + 1}`;
|
|
284
|
+
}
|
|
285
|
+
if (tier === "row-selected") {
|
|
286
|
+
return `Row ${ctx.currentCell.rowIndex + 1}`;
|
|
287
|
+
}
|
|
288
|
+
if (tier === "column-selected") {
|
|
289
|
+
return `Col ${ctx.currentCell.columnIndex + 1}`;
|
|
290
|
+
}
|
|
291
|
+
if (tier === "whole-table") {
|
|
292
|
+
return "Whole table";
|
|
293
|
+
}
|
|
294
|
+
return `${ctx.selectedCellCount} cells`;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function tierLabel(tier: TableTier): string {
|
|
298
|
+
switch (tier) {
|
|
299
|
+
case "caret-in-cell":
|
|
300
|
+
return "Cell";
|
|
301
|
+
case "multi-cell":
|
|
302
|
+
return "Cells";
|
|
303
|
+
case "row-selected":
|
|
304
|
+
return "Row";
|
|
305
|
+
case "column-selected":
|
|
306
|
+
return "Column";
|
|
307
|
+
case "whole-table":
|
|
308
|
+
return "Table";
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function tierWidthCap(tier: TableTier): string {
|
|
313
|
+
switch (tier) {
|
|
314
|
+
case "caret-in-cell":
|
|
315
|
+
return "max-w-[min(18rem,calc(100vw-1.5rem))]";
|
|
316
|
+
case "multi-cell":
|
|
317
|
+
return "max-w-[min(22rem,calc(100vw-1.5rem))]";
|
|
318
|
+
case "row-selected":
|
|
319
|
+
case "column-selected":
|
|
320
|
+
return "max-w-[min(24rem,calc(100vw-1.5rem))]";
|
|
321
|
+
case "whole-table":
|
|
322
|
+
return "max-w-[min(28rem,calc(100vw-1.5rem))]";
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
190
326
|
function ToolbarBadge(props: {
|
|
191
327
|
children: React.ReactNode;
|
|
192
328
|
tone?: "neutral" | "accent";
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chrome-overlay projector — maps `RenderFrameRect` values into CSS-absolute
|
|
3
|
+
* positions under a chrome overlay's own coordinate space.
|
|
4
|
+
*
|
|
5
|
+
* Per runtime-rendering-and-chrome-phase.md §6.2, every overlay child reads
|
|
6
|
+
* the same rect math so rails, balloons, toolbars, and handles stay aligned
|
|
7
|
+
* on scroll, zoom, and resize. The projector is a tiny pure helper so the
|
|
8
|
+
* rect math can be unit-tested and reused.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { RenderFrameRect } from "../../runtime/render/index.ts";
|
|
12
|
+
|
|
13
|
+
export interface OverlayCoordinateSpace {
|
|
14
|
+
/** Top-left of the overlay in the document column's coordinate space. */
|
|
15
|
+
originLeftPx: number;
|
|
16
|
+
originTopPx: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CSSRect {
|
|
20
|
+
left: string;
|
|
21
|
+
top: string;
|
|
22
|
+
width: string;
|
|
23
|
+
height: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Convert a RenderFrameRect (which is relative to the document column's
|
|
28
|
+
* top-left) into CSS absolute-position values relative to the overlay's own
|
|
29
|
+
* coordinate space. Output uses `px` units so consumers can pass the
|
|
30
|
+
* result straight to inline `style={{...}}`.
|
|
31
|
+
*/
|
|
32
|
+
export function projectRectToOverlay(
|
|
33
|
+
rect: RenderFrameRect,
|
|
34
|
+
space: OverlayCoordinateSpace = { originLeftPx: 0, originTopPx: 0 },
|
|
35
|
+
): CSSRect {
|
|
36
|
+
return {
|
|
37
|
+
left: `${rect.leftPx - space.originLeftPx}px`,
|
|
38
|
+
top: `${rect.topPx - space.originTopPx}px`,
|
|
39
|
+
width: `${rect.widthPx}px`,
|
|
40
|
+
height: `${rect.heightPx}px`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Expand a page-interior rect outward by `padLeftPx` / `padRightPx` so a
|
|
46
|
+
* decoration can spill into the page margin (scope rail gutter). The
|
|
47
|
+
* output rect width is adjusted accordingly.
|
|
48
|
+
*/
|
|
49
|
+
export function inflateRect(
|
|
50
|
+
rect: RenderFrameRect,
|
|
51
|
+
pad: {
|
|
52
|
+
leftPx?: number;
|
|
53
|
+
rightPx?: number;
|
|
54
|
+
topPx?: number;
|
|
55
|
+
bottomPx?: number;
|
|
56
|
+
},
|
|
57
|
+
): RenderFrameRect {
|
|
58
|
+
const padLeft = pad.leftPx ?? 0;
|
|
59
|
+
const padRight = pad.rightPx ?? 0;
|
|
60
|
+
const padTop = pad.topPx ?? 0;
|
|
61
|
+
const padBottom = pad.bottomPx ?? 0;
|
|
62
|
+
return {
|
|
63
|
+
leftPx: rect.leftPx - padLeft,
|
|
64
|
+
topPx: rect.topPx - padTop,
|
|
65
|
+
widthPx: rect.widthPx + padLeft + padRight,
|
|
66
|
+
heightPx: rect.heightPx + padTop + padBottom,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Union of two rects; returns `null` if either input is nullish so callers
|
|
72
|
+
* can safely chain lookups.
|
|
73
|
+
*/
|
|
74
|
+
export function unionRect(
|
|
75
|
+
a: RenderFrameRect | null | undefined,
|
|
76
|
+
b: RenderFrameRect | null | undefined,
|
|
77
|
+
): RenderFrameRect | null {
|
|
78
|
+
if (!a) return b ?? null;
|
|
79
|
+
if (!b) return a;
|
|
80
|
+
const left = Math.min(a.leftPx, b.leftPx);
|
|
81
|
+
const top = Math.min(a.topPx, b.topPx);
|
|
82
|
+
const right = Math.max(a.leftPx + a.widthPx, b.leftPx + b.widthPx);
|
|
83
|
+
const bottom = Math.max(a.topPx + a.heightPx, b.topPx + b.heightPx);
|
|
84
|
+
return {
|
|
85
|
+
leftPx: left,
|
|
86
|
+
topPx: top,
|
|
87
|
+
widthPx: right - left,
|
|
88
|
+
heightPx: bottom - top,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChromeOverlay public module entry.
|
|
3
|
+
*
|
|
4
|
+
* Importers should pull the overlay + layer components from here; the
|
|
5
|
+
* internal layer files may be reorganized as the chrome phase continues.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { TwChromeOverlay, type TwChromeOverlayProps } from "./tw-chrome-overlay";
|
|
9
|
+
export { TwScopeRailLayer, type TwScopeRailLayerProps } from "./tw-scope-rail-layer";
|
|
10
|
+
export {
|
|
11
|
+
TwWorkspaceViewSwitcher,
|
|
12
|
+
type TwWorkspaceViewSwitcherProps,
|
|
13
|
+
type WorkspaceView,
|
|
14
|
+
type WorkspaceViewAction,
|
|
15
|
+
} from "./tw-workspace-view-switcher";
|
|
16
|
+
export {
|
|
17
|
+
inflateRect,
|
|
18
|
+
projectRectToOverlay,
|
|
19
|
+
unionRect,
|
|
20
|
+
type CSSRect,
|
|
21
|
+
type OverlayCoordinateSpace,
|
|
22
|
+
} from "./chrome-overlay-projector";
|