Skip to content

Commit 64a877e

Browse files
Copilothotlong
andcommitted
Fix code review issues: improve clipboard API usage, optimize performance, and remove type assertions
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 2ea6523 commit 64a877e

File tree

2 files changed

+78
-61
lines changed

2 files changed

+78
-61
lines changed

packages/ui/src/components/grid/DataTable.tsx

Lines changed: 37 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -151,55 +151,47 @@ export function DataTable<TData, TValue>({
151151
}
152152

153153
// Copy/Paste functionality
154-
const handleCopy = React.useCallback((e: React.ClipboardEvent) => {
155-
if (!enableCopyPaste) return
156-
157-
const selection = window.getSelection()
158-
if (selection && selection.toString()) {
159-
// Let browser handle the copy
160-
return
161-
}
154+
const handleCopy = React.useCallback(() => {
155+
if (!enableCopyPaste || !hasSelection) return
162156

163-
// Copy selected rows as TSV
164-
if (hasSelection) {
165-
e.preventDefault()
166-
const headers = enhancedColumns
157+
const headers = enhancedColumns
158+
.filter(col => col.id !== 'select')
159+
.map(col => typeof col.header === 'string' ? col.header : col.id)
160+
.join('\t')
161+
162+
const rows = selectedRows.map(row =>
163+
enhancedColumns
167164
.filter(col => col.id !== 'select')
168-
.map(col => typeof col.header === 'string' ? col.header : col.id)
165+
.map(col => {
166+
const cell = row.getValue(col.id as string)
167+
return cell !== null && cell !== undefined ? String(cell) : ''
168+
})
169169
.join('\t')
170-
171-
const rows = selectedRows.map(row =>
172-
enhancedColumns
173-
.filter(col => col.id !== 'select')
174-
.map(col => {
175-
const cell = row.getValue(col.id as string)
176-
return cell !== null && cell !== undefined ? String(cell) : ''
177-
})
178-
.join('\t')
179-
).join('\n')
170+
).join('\n')
180171

181-
const tsv = headers + '\n' + rows
182-
e.clipboardData.setData('text/plain', tsv)
183-
}
184-
}, [enableCopyPaste, hasSelection, selectedRows, enhancedColumns])
185-
186-
// Add keyboard shortcuts for copy
187-
React.useEffect(() => {
188-
if (!enableCopyPaste) return
189-
190-
const handleKeyDown = (e: KeyboardEvent) => {
191-
if ((e.ctrlKey || e.metaKey) && e.key === 'c' && hasSelection) {
192-
// Copy will be handled by the copy event
193-
document.dispatchEvent(new ClipboardEvent('copy', {
194-
clipboardData: new DataTransfer(),
195-
bubbles: true
196-
}))
172+
const tsv = headers + '\n' + rows
173+
174+
// Use modern clipboard API with error handling
175+
if (navigator.clipboard && navigator.clipboard.writeText) {
176+
navigator.clipboard.writeText(tsv).catch(err => {
177+
console.error('Failed to copy to clipboard:', err)
178+
})
179+
} else {
180+
// Fallback for older browsers
181+
const textArea = document.createElement('textarea')
182+
textArea.value = tsv
183+
textArea.style.position = 'fixed'
184+
textArea.style.left = '-999999px'
185+
document.body.appendChild(textArea)
186+
textArea.select()
187+
try {
188+
document.execCommand('copy')
189+
} catch (err) {
190+
console.error('Failed to copy to clipboard:', err)
197191
}
192+
document.body.removeChild(textArea)
198193
}
199-
200-
document.addEventListener('keydown', handleKeyDown)
201-
return () => document.removeEventListener('keydown', handleKeyDown)
202-
}, [enableCopyPaste, hasSelection])
194+
}, [enableCopyPaste, hasSelection, selectedRows, enhancedColumns])
203195

204196
const renderFilters = () => {
205197
// If enableMultipleFilters is true and filterConfigs are provided
@@ -284,7 +276,7 @@ export function DataTable<TData, TValue>({
284276
}
285277

286278
return (
287-
<div onCopy={handleCopy}>
279+
<div>
288280
{/* Bulk actions toolbar */}
289281
{enableRowSelection && hasSelection && (
290282
<div className="flex items-center gap-2 p-2 mb-2 bg-blue-50 border border-blue-200 rounded-md">
@@ -296,16 +288,7 @@ export function DataTable<TData, TValue>({
296288
<Button
297289
variant="outline"
298290
size="sm"
299-
onClick={() => {
300-
const event = new ClipboardEvent('copy', {
301-
clipboardData: new DataTransfer(),
302-
bubbles: true
303-
})
304-
handleCopy(event as any)
305-
if (event.clipboardData) {
306-
navigator.clipboard.writeText(event.clipboardData.getData('text/plain'))
307-
}
308-
}}
291+
onClick={handleCopy}
309292
className="h-8"
310293
>
311294
<Copy className="w-4 h-4 mr-1" />

packages/ui/src/components/grid/GridView.tsx

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,17 @@ export function GridView({
6363
setColumns(initialColumns)
6464
}, [initialColumns])
6565

66-
// Group data if grouping is enabled
67-
const groupedData = React.useMemo(() => {
68-
if (!enableGrouping || !groupByColumn) return { ungrouped: data }
66+
// Group data if grouping is enabled and create index map for performance
67+
const { groupedData, rowIndexMap } = React.useMemo(() => {
68+
// Create a map for O(1) index lookups
69+
const indexMap = new Map<any, number>()
70+
data.forEach((row, index) => {
71+
indexMap.set(row, index)
72+
})
73+
74+
if (!enableGrouping || !groupByColumn) {
75+
return { groupedData: { ungrouped: data }, rowIndexMap: indexMap }
76+
}
6977

7078
const groups: Record<string, any[]> = {}
7179
data.forEach(row => {
@@ -75,13 +83,18 @@ export function GridView({
7583
}
7684
groups[groupValue].push(row)
7785
})
78-
return groups
86+
return { groupedData: groups, rowIndexMap: indexMap }
7987
}, [data, enableGrouping, groupByColumn])
8088

8189
const [expandedGroups, setExpandedGroups] = React.useState<Set<string>>(
8290
new Set(Object.keys(groupedData))
8391
)
8492

93+
// Update expanded groups when groupedData changes
94+
React.useEffect(() => {
95+
setExpandedGroups(new Set(Object.keys(groupedData)))
96+
}, [groupedData])
97+
8598
const toggleGroup = (groupKey: string) => {
8699
setExpandedGroups(prev => {
87100
const newSet = new Set(prev)
@@ -139,7 +152,28 @@ export function GridView({
139152
.join('\n')
140153

141154
const tsv = headers + '\n' + rows
142-
navigator.clipboard.writeText(tsv)
155+
156+
// Use modern clipboard API with error handling
157+
if (navigator.clipboard && navigator.clipboard.writeText) {
158+
navigator.clipboard.writeText(tsv).catch(err => {
159+
console.error('Failed to copy to clipboard:', err)
160+
// Fallback: show user a message or use document.execCommand as last resort
161+
})
162+
} else {
163+
// Fallback for older browsers
164+
const textArea = document.createElement('textarea')
165+
textArea.value = tsv
166+
textArea.style.position = 'fixed'
167+
textArea.style.left = '-999999px'
168+
document.body.appendChild(textArea)
169+
textArea.select()
170+
try {
171+
document.execCommand('copy')
172+
} catch (err) {
173+
console.error('Failed to copy to clipboard:', err)
174+
}
175+
document.body.removeChild(textArea)
176+
}
143177
}
144178

145179
// Column drag and drop handlers
@@ -404,8 +438,8 @@ export function GridView({
404438
</td>
405439
</tr>
406440
{expandedGroups.has(groupKey) &&
407-
groupRows.map((row, rowIndex) => {
408-
const actualIndex = data.indexOf(row)
441+
groupRows.map((row) => {
442+
const actualIndex = rowIndexMap.get(row) ?? -1
409443
return (
410444
<tr
411445
key={actualIndex}

0 commit comments

Comments
 (0)