Skip to content

Commit 414bda9

Browse files
feat: add remove duplicates (#398)
* feat: add remove duplicates with Google Sheets parity. Implement engine module, dialog UI, and publish as 2.1.0-remove. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: restore post-merge shortcut matcher guards for modal and formula list Prevent Cmd+Shift+Option+; from opening the shortcuts modal, disambiguate US Cmd+Shift+5 from AZERTY formula-list, and bump to 2.1.1-remove-1. Co-authored-by: Cursor <cursoragent@cursor.com> * minor changes --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 6717e40 commit 414bda9

10 files changed

Lines changed: 909 additions & 7 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@fileverse-dev/dsheet",
33
"private": false,
44
"description": "DSheet",
5-
"version": "2.1.1",
5+
"version": "2.1.2",
66
"main": "dist/index.es.js",
77
"module": "dist/index.es.js",
88
"exports": {

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ export {
4949
createFilter,
5050
clearFilter,
5151
handleSort,
52+
getRemoveDuplicatesPreview,
53+
removeDuplicates,
54+
getRemoveDuplicatesErrorMessage,
5255
// edit-menu.tsx
5356
handleCopy,
5457
handlePasteByClick,

src/sheet-engine/core/events/keyboard-shortcut-utils.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,13 @@ export function isInsertDateTimeShortcut(e: KeyboardEvent): boolean {
125125
export function isUsInsertDateTimeQuoteShortcut(e: KeyboardEvent): boolean {
126126
if (!hasMod(e) || !e.shiftKey || e.altKey) return false;
127127
if (isImeOrDeadKey(e)) return false;
128+
// Number formats (§8) share Ctrl/Cmd+Shift+digit chords — must win over insert date/time.
129+
if (isNumberFormatShortcut(e)) return false;
128130

129131
if (e.key === '"') return true;
130132
if (!isQuoteTypingCode(e.code)) return false;
133+
// Physical quote-key slots also type digits 1–9 with Shift — not insert date/time.
134+
if (/^[0-9]$/.test(e.key)) return false;
131135

132136
// ⌘+Shift+3 is macOS full-screen screenshot — browser never receives Digit3.
133137
if (
@@ -201,6 +205,12 @@ export function isDigitFormatKey(e: KeyboardEvent, digit: string): boolean {
201205
return e.code === `Digit${digit}` || e.code === `Numpad${digit}`;
202206
}
203207

208+
/** Ctrl/Cmd+Shift+1–6 — number format (TEC-2311 §8). */
209+
export function isNumberFormatShortcut(e: KeyboardEvent): boolean {
210+
if (!isNumberFormatModifier(e)) return false;
211+
return ['1', '2', '3', '4', '5', '6'].some((d) => isDigitFormatKey(e, d));
212+
}
213+
204214
/** Physical codes commonly used to type `)` (formula list). */
205215
const CLOSE_PAREN_TYPING_CODES = new Set([
206216
'BracketRight',
@@ -224,7 +234,12 @@ export function isFormulaListShortcut(e: KeyboardEvent): boolean {
224234
return e.code === 'BracketRight';
225235
}
226236

227-
return CLOSE_PAREN_TYPING_CODES.has(e.code);
237+
if (!CLOSE_PAREN_TYPING_CODES.has(e.code)) return false;
238+
// US Shift+5 is `%` (number format). AZERTY Shift+5 is `)`.
239+
if (e.code === 'Digit5' || e.code === 'Numpad5') {
240+
return e.key === ')';
241+
}
242+
return true;
228243
}
229244

230245
export function isFindShortcut(e: KeyboardEvent): boolean {
@@ -251,13 +266,14 @@ export function isFindReplaceShortcut(e: KeyboardEvent): boolean {
251266
export function isBrowserZoomShortcut(e: KeyboardEvent): boolean {
252267
if (!(e.metaKey || e.ctrlKey) || e.altKey) return false;
253268
if (isImeOrDeadKey(e)) return false;
269+
if (isNumberFormatShortcut(e)) return false;
254270

255271
if (
256272
e.code === 'Equal' ||
257273
e.code === 'NumpadAdd' ||
258274
e.code === 'Minus' ||
259275
e.code === 'NumpadSubtract' ||
260-
e.code === 'Digit6'
276+
(e.code === 'Digit6' && !e.shiftKey)
261277
) {
262278
return true;
263279
}
@@ -310,7 +326,7 @@ function isWinCtrlSlashShortcut(e: KeyboardEvent): boolean {
310326
*/
311327
function isOptionSlashOrColonShortcut(e: KeyboardEvent): boolean {
312328
if (e.getModifierState?.('AltGraph')) return false;
313-
if (!e.altKey || e.ctrlKey) return false;
329+
if (!e.altKey || e.ctrlKey || e.metaKey) return false;
314330
if (isImeOrDeadKey(e)) return false;
315331

316332
if (e.key === '/' || e.key === ':') return true;
@@ -328,6 +344,7 @@ export function isOpenShortcutsModalShortcut(e: KeyboardEvent): boolean {
328344
if (isImeOrDeadKey(e)) return false;
329345
if (isBrowserZoomShortcut(e)) return false;
330346
if (isFormulaListShortcut(e)) return false;
347+
if (isInsertDateTimeShortcut(e)) return false;
331348

332349
if (isWinCtrlSlashShortcut(e)) return true;
333350
if (isOptionSlashOrColonShortcut(e)) return true;

src/sheet-engine/core/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ export {
178178
handleMerge,
179179
mergeSelectionHasValues,
180180
handleSort,
181+
getRemoveDuplicatesPreview,
182+
removeDuplicates,
183+
getRemoveDuplicatesErrorMessage,
181184
handleFreeze,
182185
handleTextSize,
183186
handleSum,
@@ -366,6 +369,14 @@ export type {
366369
Freezen,
367370
} from './types';
368371

372+
export type {
373+
RemoveDuplicatesColumnOption,
374+
RemoveDuplicatesError,
375+
RemoveDuplicatesOptions,
376+
RemoveDuplicatesPreview,
377+
RemoveDuplicatesResult,
378+
} from './modules';
379+
369380
export type {
370381
CheckModes,
371382
HyperlinkMap,

src/sheet-engine/core/locale/en.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12077,6 +12077,21 @@ export default {
1207712077

1207812078
selectCategory: 'Or select a category',
1207912079
},
12080+
removeDuplicates: {
12081+
title: 'Remove duplicates',
12082+
dataHasHeaderRow: 'Data has header row',
12083+
columnsToAnalyze: 'Columns to analyze',
12084+
selectAll: 'Select all',
12085+
removeDuplicates: 'Remove duplicates',
12086+
result:
12087+
'{removed} duplicate rows found and removed. {remaining} unique rows remain.',
12088+
noMulti:
12089+
'Cannot perform this operation on multiple selection areas, please select a single area',
12090+
noMerge: 'This operation cannot be performed on merged cells',
12091+
noColumns: 'Select at least one column to analyze',
12092+
noSelection: 'Please select the scope of the operation',
12093+
readOnly: 'Cannot remove duplicates in read-only mode',
12094+
},
1208012095
drag: {
1208112096
noMerge: 'Cannot perform this operation on merged cells',
1208212097
affectPivot:

src/sheet-engine/core/modules/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,20 @@ export {
229229
// merge
230230
export { mergeCells, mergeSelectionHasValues } from './merge';
231231

232+
// removeDuplicates
233+
export {
234+
getRemoveDuplicatesPreview,
235+
removeDuplicates,
236+
getRemoveDuplicatesErrorMessage,
237+
} from './removeDuplicates';
238+
export type {
239+
RemoveDuplicatesColumnOption,
240+
RemoveDuplicatesError,
241+
RemoveDuplicatesOptions,
242+
RemoveDuplicatesPreview,
243+
RemoveDuplicatesResult,
244+
} from './removeDuplicates';
245+
232246
// sort
233247
export {
234248
sortSelection,

0 commit comments

Comments
 (0)