@xcelsior/ui-spreadsheets 1.2.2 → 1.3.0
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/.omc/state/agent-replay-0cead415-b3bd-40fd-b199-47371946c4db.jsonl +25 -0
- package/.omc/state/idle-notif-cooldown.json +3 -0
- package/.omc/state/last-tool-error.json +7 -0
- package/.omc/state/mission-state.json +179 -0
- package/.omc/state/subagent-tracking.json +116 -0
- package/.turbo/turbo-build.log +28 -28
- package/.turbo/turbo-lint.log +140 -0
- package/dist/index.d.mts +94 -4
- package/dist/index.d.ts +94 -4
- package/dist/index.js +2133 -1156
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2023 -1048
- package/dist/index.mjs.map +1 -1
- package/dist/styles/globals.css +156 -16
- package/dist/styles/globals.css.map +1 -1
- package/package.json +1 -1
- package/plans/20260330-1230-spreadsheet-features/phase-01-types-and-duplicates-hook.md +73 -0
- package/plans/20260330-1230-spreadsheet-features/phase-02-filter-dropdown-portal.md +90 -0
- package/plans/20260330-1230-spreadsheet-features/phase-03-header-overflow-menu.md +101 -0
- package/plans/20260330-1230-spreadsheet-features/phase-04-integration.md +193 -0
- package/plans/20260330-1230-spreadsheet-features/plan.md +59 -0
- package/src/components/ColorPickerPopover.tsx +77 -32
- package/src/components/ColumnHeaderActions.tsx +241 -1
- package/src/components/RowIndexColumnHeader.tsx +13 -17
- package/src/components/SelectionSummaryBar.tsx +103 -0
- package/src/components/Spreadsheet.stories.tsx +254 -0
- package/src/components/Spreadsheet.tsx +234 -189
- package/src/components/SpreadsheetCell.tsx +280 -42
- package/src/components/SpreadsheetFilterDropdown.tsx +178 -13
- package/src/components/SpreadsheetHeader.tsx +79 -24
- package/src/components/SpreadsheetSettingsModal.tsx +4 -0
- package/src/hooks/useSpreadsheetColumnResize.ts +143 -0
- package/src/hooks/useSpreadsheetDuplicates.ts +149 -0
- package/src/hooks/useSpreadsheetFiltering.ts +18 -1
- package/src/hooks/useSpreadsheetHighlighting.ts +23 -3
- package/src/hooks/useSpreadsheetKeyboardShortcuts.ts +16 -0
- package/src/hooks/useSpreadsheetPinning.ts +148 -134
- package/src/hooks/useSpreadsheetSelection.ts +10 -22
- package/src/hooks/useSpreadsheetSummary.ts +68 -0
- package/src/index.ts +4 -1
- package/src/styles/globals.css +51 -0
- package/src/types.ts +50 -2
- package/storybook-static/assets/Color-YHDXOIA2-CtQurLnT.js +1 -0
- package/storybook-static/assets/DocsRenderer-CFRXHY34-oxrW8Hvo.js +575 -0
- package/storybook-static/assets/Spreadsheet.stories-DvhhzuK4.js +1357 -0
- package/storybook-static/assets/chunk-XP5HYGXS-BpfKkqn7.js +1 -0
- package/storybook-static/assets/entry-preview-CkBGHCAN.js +2 -0
- package/storybook-static/assets/entry-preview-docs-ugJb6pa8.js +46 -0
- package/storybook-static/assets/iframe-CPp2u3vg.js +211 -0
- package/storybook-static/assets/index-BB9bPxRC.js +24 -0
- package/storybook-static/assets/index-BQFlzFLk.js +9 -0
- package/storybook-static/assets/index-CtvPRVHf.js +9 -0
- package/storybook-static/assets/index-DgH-xKnr.js +11 -0
- package/storybook-static/assets/index-DrFu-skq.js +6 -0
- package/storybook-static/assets/index-DrdPSA1J.js +240 -0
- package/storybook-static/assets/index-DzFBShOR.js +20 -0
- package/storybook-static/assets/index-v-1boR4t.js +1 -0
- package/storybook-static/assets/preview-B8lJiyuQ.js +34 -0
- package/storybook-static/assets/preview-BBWR9nbA.js +1 -0
- package/storybook-static/assets/preview-BWzBA1C2.js +396 -0
- package/storybook-static/assets/preview-Bm0S-uxO.css +1 -0
- package/storybook-static/assets/preview-CvbIS5ZJ.js +1 -0
- package/storybook-static/assets/preview-DD_OYowb.js +1 -0
- package/storybook-static/assets/preview-DGUiP6tS.js +7 -0
- package/storybook-static/assets/preview-DHQbi4pV.js +1 -0
- package/storybook-static/assets/preview-DwI0w3cI.js +1 -0
- package/storybook-static/assets/preview-DyR7iiFG.js +1 -0
- package/storybook-static/assets/preview-zxZ6Be2V.js +2 -0
- package/storybook-static/assets/react-18-Pj8skaX9.js +1 -0
- package/storybook-static/assets/test-utils-quxJ1Z79.js +9 -0
- package/storybook-static/favicon.svg +1 -0
- package/storybook-static/iframe.html +666 -0
- package/storybook-static/index.html +177 -0
- package/storybook-static/index.json +1 -0
- package/storybook-static/nunito-sans-bold-italic.woff2 +0 -0
- package/storybook-static/nunito-sans-bold.woff2 +0 -0
- package/storybook-static/nunito-sans-italic.woff2 +0 -0
- package/storybook-static/nunito-sans-regular.woff2 +0 -0
- package/storybook-static/project.json +1 -0
- package/storybook-static/sb-addons/essentials-actions-3/manager-bundle.js +3 -0
- package/storybook-static/sb-addons/essentials-backgrounds-5/manager-bundle.js +12 -0
- package/storybook-static/sb-addons/essentials-controls-2/manager-bundle.js +405 -0
- package/storybook-static/sb-addons/essentials-docs-4/manager-bundle.js +245 -0
- package/storybook-static/sb-addons/essentials-measure-8/manager-bundle.js +3 -0
- package/storybook-static/sb-addons/essentials-outline-9/manager-bundle.js +3 -0
- package/storybook-static/sb-addons/essentials-toolbars-7/manager-bundle.js +3 -0
- package/storybook-static/sb-addons/essentials-viewport-6/manager-bundle.js +3 -0
- package/storybook-static/sb-addons/interactions-10/manager-bundle.js +222 -0
- package/storybook-static/sb-addons/links-1/manager-bundle.js +3 -0
- package/storybook-static/sb-addons/storybook-core-core-server-presets-0/common-manager-bundle.js +3 -0
- package/storybook-static/sb-common-assets/favicon.svg +1 -0
- package/storybook-static/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
- package/storybook-static/sb-common-assets/nunito-sans-bold.woff2 +0 -0
- package/storybook-static/sb-common-assets/nunito-sans-italic.woff2 +0 -0
- package/storybook-static/sb-common-assets/nunito-sans-regular.woff2 +0 -0
- package/storybook-static/sb-manager/globals-module-info.js +1052 -0
- package/storybook-static/sb-manager/globals-runtime.js +42127 -0
- package/storybook-static/sb-manager/globals.js +48 -0
- package/storybook-static/sb-manager/runtime.js +12048 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import type { SpreadsheetColumn } from '../types';
|
|
3
|
+
|
|
4
|
+
export interface UseSpreadsheetDuplicatesOptions<T> {
|
|
5
|
+
data: T[];
|
|
6
|
+
columns: SpreadsheetColumn<T>[];
|
|
7
|
+
duplicateCheckColumns: string[];
|
|
8
|
+
getRowId: (row: T) => string | number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface UseSpreadsheetDuplicatesReturn {
|
|
12
|
+
/** Check if a specific cell value is a duplicate in its column */
|
|
13
|
+
isCellDuplicate: (rowId: string | number, columnId: string) => boolean;
|
|
14
|
+
/** Get the duplicate count for a value in a column */
|
|
15
|
+
getDuplicateCount: (columnId: string, value: any) => number;
|
|
16
|
+
/** Get the number of rows with duplicate values for a column */
|
|
17
|
+
getDuplicateColumnCount: (columnId: string) => number;
|
|
18
|
+
/** Map of columnId -> Set of rowIds that have duplicate values */
|
|
19
|
+
duplicateRowIds: Map<string, Set<string | number>>;
|
|
20
|
+
/** Set of column IDs with duplicate checking enabled */
|
|
21
|
+
duplicateCheckColumns: Set<string>;
|
|
22
|
+
/** Toggle duplicate check for a column */
|
|
23
|
+
toggleDuplicateCheck: (columnId: string) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Normalize a cell value for duplicate comparison */
|
|
27
|
+
function normalizeValue(value: any): string {
|
|
28
|
+
if (value === null || value === undefined || value === '') {
|
|
29
|
+
return '__blank__';
|
|
30
|
+
}
|
|
31
|
+
if (typeof value === 'number') {
|
|
32
|
+
return String(value);
|
|
33
|
+
}
|
|
34
|
+
return String(value).trim().toLowerCase();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function useSpreadsheetDuplicates<T>({
|
|
38
|
+
data,
|
|
39
|
+
columns,
|
|
40
|
+
duplicateCheckColumns,
|
|
41
|
+
getRowId,
|
|
42
|
+
}: UseSpreadsheetDuplicatesOptions<T>): UseSpreadsheetDuplicatesReturn {
|
|
43
|
+
const [localDuplicateCheckColumns, setLocalDuplicateCheckColumns] = useState<Set<string>>(
|
|
44
|
+
() => new Set(duplicateCheckColumns)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Sync external prop changes into local state
|
|
48
|
+
const duplicateCheckColumnsSet = useMemo(() => {
|
|
49
|
+
return new Set(duplicateCheckColumns);
|
|
50
|
+
}, [duplicateCheckColumns]);
|
|
51
|
+
|
|
52
|
+
// Build Maps for O(1) lookup: columnId -> normalizedValue -> rowId[]
|
|
53
|
+
const { duplicateRowIds, valueCounts } = useMemo(() => {
|
|
54
|
+
const duplicateRowIds = new Map<string, Set<string | number>>();
|
|
55
|
+
const valueCounts = new Map<string, Map<string, number>>();
|
|
56
|
+
|
|
57
|
+
const activeColumns = duplicateCheckColumnsSet.size > 0
|
|
58
|
+
? duplicateCheckColumnsSet
|
|
59
|
+
: localDuplicateCheckColumns;
|
|
60
|
+
|
|
61
|
+
for (const columnId of activeColumns) {
|
|
62
|
+
const column = columns.find((c) => c.id === columnId);
|
|
63
|
+
if (!column) continue;
|
|
64
|
+
|
|
65
|
+
// Single O(n) pass: build value -> rowId[] map
|
|
66
|
+
const valueToRowIds = new Map<string, (string | number)[]>();
|
|
67
|
+
|
|
68
|
+
for (const row of data) {
|
|
69
|
+
const rawValue = column.getValue ? column.getValue(row) : (row as any)[columnId];
|
|
70
|
+
// Skip empty/null/undefined values — they're not duplicates
|
|
71
|
+
if (rawValue === null || rawValue === undefined || rawValue === '') continue;
|
|
72
|
+
const normalized = normalizeValue(rawValue);
|
|
73
|
+
const rowId = getRowId(row);
|
|
74
|
+
|
|
75
|
+
const existing = valueToRowIds.get(normalized);
|
|
76
|
+
if (existing) {
|
|
77
|
+
existing.push(rowId);
|
|
78
|
+
} else {
|
|
79
|
+
valueToRowIds.set(normalized, [rowId]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Build duplicateRowIds set for this column
|
|
84
|
+
const dupSet = new Set<string | number>();
|
|
85
|
+
const countMap = new Map<string, number>();
|
|
86
|
+
|
|
87
|
+
for (const [normalizedVal, rowIds] of valueToRowIds) {
|
|
88
|
+
countMap.set(normalizedVal, rowIds.length);
|
|
89
|
+
if (rowIds.length >= 2) {
|
|
90
|
+
for (const rowId of rowIds) {
|
|
91
|
+
dupSet.add(rowId);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
duplicateRowIds.set(columnId, dupSet);
|
|
97
|
+
valueCounts.set(columnId, countMap);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return { duplicateRowIds, valueCounts };
|
|
101
|
+
}, [data, columns, duplicateCheckColumnsSet, localDuplicateCheckColumns, getRowId]);
|
|
102
|
+
|
|
103
|
+
const isCellDuplicate = useCallback(
|
|
104
|
+
(rowId: string | number, columnId: string): boolean => {
|
|
105
|
+
return duplicateRowIds.get(columnId)?.has(rowId) ?? false;
|
|
106
|
+
},
|
|
107
|
+
[duplicateRowIds]
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const getDuplicateCount = useCallback(
|
|
111
|
+
(columnId: string, value: any): number => {
|
|
112
|
+
const normalized = normalizeValue(value);
|
|
113
|
+
return valueCounts.get(columnId)?.get(normalized) ?? 0;
|
|
114
|
+
},
|
|
115
|
+
[valueCounts]
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const getDuplicateColumnCount = useCallback(
|
|
119
|
+
(columnId: string): number => {
|
|
120
|
+
return duplicateRowIds.get(columnId)?.size ?? 0;
|
|
121
|
+
},
|
|
122
|
+
[duplicateRowIds]
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const toggleDuplicateCheck = useCallback((columnId: string) => {
|
|
126
|
+
setLocalDuplicateCheckColumns((prev) => {
|
|
127
|
+
const next = new Set(prev);
|
|
128
|
+
if (next.has(columnId)) {
|
|
129
|
+
next.delete(columnId);
|
|
130
|
+
} else {
|
|
131
|
+
next.add(columnId);
|
|
132
|
+
}
|
|
133
|
+
return next;
|
|
134
|
+
});
|
|
135
|
+
}, []);
|
|
136
|
+
|
|
137
|
+
// Expose the effective set (external prop wins when non-empty, else local state)
|
|
138
|
+
const effectiveDuplicateCheckColumns =
|
|
139
|
+
duplicateCheckColumnsSet.size > 0 ? duplicateCheckColumnsSet : localDuplicateCheckColumns;
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
isCellDuplicate,
|
|
143
|
+
getDuplicateCount,
|
|
144
|
+
getDuplicateColumnCount,
|
|
145
|
+
duplicateRowIds,
|
|
146
|
+
duplicateCheckColumns: effectiveDuplicateCheckColumns,
|
|
147
|
+
toggleDuplicateCheck,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
@@ -25,6 +25,13 @@ export interface UseSpreadsheetFilteringOptions<T> {
|
|
|
25
25
|
* Use with serverSide mode for server-side sorting.
|
|
26
26
|
*/
|
|
27
27
|
controlledSortConfig?: SpreadsheetSortConfig | null;
|
|
28
|
+
/**
|
|
29
|
+
* Map of columnId -> Set of rowIds that have duplicate values.
|
|
30
|
+
* Used for showDuplicatesOnly filter option.
|
|
31
|
+
*/
|
|
32
|
+
duplicateRowIds?: Map<string, Set<string | number>>;
|
|
33
|
+
/** Row ID accessor, required when using showDuplicatesOnly filter */
|
|
34
|
+
getRowId?: (row: T) => string | number;
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
export interface UseSpreadsheetFilteringReturn<T> {
|
|
@@ -54,6 +61,8 @@ export function useSpreadsheetFiltering<T extends Record<string, any>>({
|
|
|
54
61
|
controlledFilters,
|
|
55
62
|
controlledSortConfig,
|
|
56
63
|
defaultSortConfig,
|
|
64
|
+
duplicateRowIds,
|
|
65
|
+
getRowId,
|
|
57
66
|
}: UseSpreadsheetFilteringOptions<T>): UseSpreadsheetFilteringReturn<T> {
|
|
58
67
|
// Internal state for uncontrolled mode
|
|
59
68
|
const [internalFilters, setInternalFilters] = useState<Record<string, SpreadsheetColumnFilter>>(
|
|
@@ -337,6 +346,14 @@ export function useSpreadsheetFiltering<T extends Record<string, any>>({
|
|
|
337
346
|
if (!column) continue;
|
|
338
347
|
|
|
339
348
|
filterChain.add(buildFilterPredicate(column, filter));
|
|
349
|
+
|
|
350
|
+
// Handle showDuplicatesOnly as an additional predicate
|
|
351
|
+
if (filter.showDuplicatesOnly && duplicateRowIds && getRowId) {
|
|
352
|
+
const dupSet = duplicateRowIds.get(columnId);
|
|
353
|
+
if (dupSet) {
|
|
354
|
+
filterChain.add((row: T) => dupSet.has(getRowId(row)));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
340
357
|
}
|
|
341
358
|
|
|
342
359
|
// Apply combined filter if any filters are active
|
|
@@ -351,7 +368,7 @@ export function useSpreadsheetFiltering<T extends Record<string, any>>({
|
|
|
351
368
|
}
|
|
352
369
|
|
|
353
370
|
return lazyResult;
|
|
354
|
-
}, [data, filters, sortConfig, columns, serverSide, buildFilterPredicate, buildSortComparator]);
|
|
371
|
+
}, [data, filters, sortConfig, columns, serverSide, buildFilterPredicate, buildSortComparator, duplicateRowIds, getRowId]);
|
|
355
372
|
|
|
356
373
|
const handleFilterChange = useCallback(
|
|
357
374
|
(columnId: string, filter: SpreadsheetColumnFilter | undefined) => {
|
|
@@ -72,6 +72,9 @@ export interface UseSpreadsheetHighlightingReturn {
|
|
|
72
72
|
highlightPickerCell: { rowId: string | number; columnId: string } | null;
|
|
73
73
|
setHighlightPickerCell: (cell: { rowId: string | number; columnId: string } | null) => void;
|
|
74
74
|
|
|
75
|
+
// Recent colors
|
|
76
|
+
recentColors: string[];
|
|
77
|
+
|
|
75
78
|
// Utility
|
|
76
79
|
clearAllHighlights: () => void;
|
|
77
80
|
}
|
|
@@ -95,6 +98,17 @@ export function useSpreadsheetHighlighting({
|
|
|
95
98
|
Record<string, string>
|
|
96
99
|
>({});
|
|
97
100
|
|
|
101
|
+
// Recent colors (track last 8 used colors)
|
|
102
|
+
const [recentColors, setRecentColors] = useState<string[]>([]);
|
|
103
|
+
|
|
104
|
+
const addRecentColor = useCallback((color: string | null) => {
|
|
105
|
+
if (!color) return;
|
|
106
|
+
setRecentColors((prev) => {
|
|
107
|
+
const filtered = prev.filter((c) => c !== color);
|
|
108
|
+
return [color, ...filtered].slice(0, 8);
|
|
109
|
+
});
|
|
110
|
+
}, []);
|
|
111
|
+
|
|
98
112
|
// Picker states
|
|
99
113
|
const [highlightPickerRow, setHighlightPickerRow] = useState<string | number | null>(null);
|
|
100
114
|
const [highlightPickerColumn, setHighlightPickerColumn] = useState<string | null>(null);
|
|
@@ -144,9 +158,10 @@ export function useSpreadsheetHighlighting({
|
|
|
144
158
|
return prev;
|
|
145
159
|
});
|
|
146
160
|
}
|
|
161
|
+
addRecentColor(color);
|
|
147
162
|
setHighlightPickerCell(null);
|
|
148
163
|
},
|
|
149
|
-
[onCellHighlight]
|
|
164
|
+
[onCellHighlight, addRecentColor]
|
|
150
165
|
);
|
|
151
166
|
|
|
152
167
|
// Get row-level highlight
|
|
@@ -181,9 +196,10 @@ export function useSpreadsheetHighlighting({
|
|
|
181
196
|
return prev;
|
|
182
197
|
});
|
|
183
198
|
}
|
|
199
|
+
addRecentColor(color);
|
|
184
200
|
setHighlightPickerRow(null);
|
|
185
201
|
},
|
|
186
|
-
[onRowHighlight]
|
|
202
|
+
[onRowHighlight, addRecentColor]
|
|
187
203
|
);
|
|
188
204
|
|
|
189
205
|
// Get column highlight (works for both regular columns and row index)
|
|
@@ -212,9 +228,10 @@ export function useSpreadsheetHighlighting({
|
|
|
212
228
|
return newHighlights;
|
|
213
229
|
});
|
|
214
230
|
}
|
|
231
|
+
addRecentColor(color);
|
|
215
232
|
setHighlightPickerColumn(null);
|
|
216
233
|
},
|
|
217
|
-
[onColumnHighlight]
|
|
234
|
+
[onColumnHighlight, addRecentColor]
|
|
218
235
|
);
|
|
219
236
|
|
|
220
237
|
// Clear all highlights
|
|
@@ -240,6 +257,9 @@ export function useSpreadsheetHighlighting({
|
|
|
240
257
|
getColumnHighlight,
|
|
241
258
|
handleColumnHighlightToggle,
|
|
242
259
|
|
|
260
|
+
// Recent colors
|
|
261
|
+
recentColors,
|
|
262
|
+
|
|
243
263
|
// Picker state
|
|
244
264
|
highlightPickerRow,
|
|
245
265
|
setHighlightPickerRow,
|
|
@@ -29,6 +29,8 @@ export interface UseSpreadsheetKeyboardShortcutsOptions {
|
|
|
29
29
|
onCopy?: () => void;
|
|
30
30
|
/** Handler for paste (Ctrl/Cmd+V) */
|
|
31
31
|
onPaste?: () => void;
|
|
32
|
+
/** Handler for select all (Ctrl/Cmd+A) */
|
|
33
|
+
onSelectAll?: () => void;
|
|
32
34
|
/** Whether there is a focused cell */
|
|
33
35
|
hasFocusedCell?: boolean;
|
|
34
36
|
/** Whether a cell is currently being edited */
|
|
@@ -69,6 +71,7 @@ export function useSpreadsheetKeyboardShortcuts({
|
|
|
69
71
|
onTabNavigation,
|
|
70
72
|
onCopy,
|
|
71
73
|
onPaste,
|
|
74
|
+
onSelectAll,
|
|
72
75
|
hasFocusedCell = false,
|
|
73
76
|
isEditing = false,
|
|
74
77
|
customShortcuts = [],
|
|
@@ -119,6 +122,17 @@ export function useSpreadsheetKeyboardShortcuts({
|
|
|
119
122
|
return;
|
|
120
123
|
}
|
|
121
124
|
|
|
125
|
+
// Cmd/Ctrl+A: Select all (only when not editing)
|
|
126
|
+
if (
|
|
127
|
+
(event.metaKey || event.ctrlKey) &&
|
|
128
|
+
(event.key === 'a' || event.key === 'A') &&
|
|
129
|
+
!isEditing
|
|
130
|
+
) {
|
|
131
|
+
event.preventDefault();
|
|
132
|
+
onSelectAll?.();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
122
136
|
// Cmd/Ctrl+C: Copy (only when not editing and has focused cell)
|
|
123
137
|
if (
|
|
124
138
|
(event.metaKey || event.ctrlKey) &&
|
|
@@ -211,6 +225,7 @@ export function useSpreadsheetKeyboardShortcuts({
|
|
|
211
225
|
onTabNavigation,
|
|
212
226
|
onCopy,
|
|
213
227
|
onPaste,
|
|
228
|
+
onSelectAll,
|
|
214
229
|
hasFocusedCell,
|
|
215
230
|
isEditing,
|
|
216
231
|
customShortcuts,
|
|
@@ -230,6 +245,7 @@ export function useSpreadsheetKeyboardShortcuts({
|
|
|
230
245
|
editing: [
|
|
231
246
|
{ label: 'Undo', keys: [modifierKey, 'Z'] },
|
|
232
247
|
{ label: 'Redo', keys: [modifierKey, 'Shift', 'Z'] },
|
|
248
|
+
{ label: 'Select all', keys: [modifierKey, 'A'] },
|
|
233
249
|
{ label: 'Copy cells', keys: [modifierKey, 'C'] },
|
|
234
250
|
{ label: 'Paste cells', keys: [modifierKey, 'V'] },
|
|
235
251
|
{ label: 'Confirm cell edit', keys: ['Enter'] },
|