Skip to content

Commit 53e35dd

Browse files
authored
fix: DH-19210: Add plaintext paste key handling (#2661)
For DH-19210. Adds handling for `text/plain` from the clipboard(this is necessary to handle copy/paste workflows from excel as `text/html` contains only '#' symbols when the column is truncated)
1 parent bd02082 commit 53e35dd

2 files changed

Lines changed: 66 additions & 9 deletions

File tree

packages/grid/src/key-handlers/PasteKeyHandler.test.tsx

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { render } from '@testing-library/react';
2-
import { parseValueFromElement } from './PasteKeyHandler';
2+
import { parseValueFromElement, parseValueFromText } from './PasteKeyHandler';
33

44
function makeElementFromJsx(jsx: JSX.Element): HTMLElement {
55
return render(jsx).container;
@@ -130,3 +130,35 @@ describe('text parsing', () => {
130130
testHtml(<div>foo</div>, 'foo');
131131
});
132132
});
133+
134+
describe('parseValueFromText', () => {
135+
it('parses a single numeric value', () => {
136+
expect(parseValueFromText('12345')).toBe('12345');
137+
});
138+
139+
it('trims whitespace for single value', () => {
140+
expect(parseValueFromText(' 3.14 \n')).toBe('3.14');
141+
});
142+
143+
it('parses a table with multiple rows and columns', () => {
144+
expect(
145+
parseValueFromText('12345\t3.14\thello\n67890\t2.71\tworld\n')
146+
).toEqual([
147+
['12345', '3.14', 'hello'],
148+
['67890', '2.71', 'world'],
149+
]);
150+
});
151+
152+
it('parses a single row from text', () => {
153+
expect(parseValueFromText('12345\t67890\t99999')).toEqual([
154+
['12345', '67890', '99999'],
155+
]);
156+
});
157+
158+
it('trims trailing newline without creating an empty row', () => {
159+
expect(parseValueFromText('A\tB\n1\t2\n')).toEqual([
160+
['A', 'B'],
161+
['1', '2'],
162+
]);
163+
});
164+
});

packages/grid/src/key-handlers/PasteKeyHandler.ts

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,17 @@ export function parseValueFromNodes(nodes: NodeListOf<ChildNode>): string[][] {
4545
return result;
4646
}
4747

48+
export function parseValueFromText(text: string): string | string[][] {
49+
const rows = text
50+
.trim()
51+
.split('\n')
52+
.map(row => row.split('\t'));
53+
if (rows.length === 1 && rows[0].length === 1) {
54+
return rows[0][0];
55+
}
56+
return rows;
57+
}
58+
4859
export function parseValueFromElement(
4960
element: HTMLElement
5061
): string | string[][] | null {
@@ -104,21 +115,35 @@ class PasteKeyHandler extends KeyHandler {
104115
'clip-path: "inset(50%)"; height: 1px; width: 1px; margin: -1px; overflow: hidden; padding 0; position: absolute;'
105116
);
106117

107-
const listener = (): void => {
108-
dummyInput.removeEventListener('input', listener);
118+
const cleanup = (): void => {
119+
dummyInput.removeEventListener('paste', pasteListener);
120+
dummyInput.removeEventListener('input', inputListener);
109121
dummyInput.remove();
110-
111122
grid.focus();
112-
const value = parseValueFromElement(dummyInput);
123+
};
124+
125+
let plainText = '';
126+
127+
// Capture text/plain from the clipboard during the paste event.
128+
// HTML element parsing is used as a fallback if text/plain is unavailable.
129+
const pasteListener = (e: Event): void => {
130+
const clipboardEvent = e as ClipboardEvent;
131+
plainText =
132+
clipboardEvent.clipboardData?.getData('text/plain') ?? '';
133+
};
134+
135+
const inputListener = (): void => {
136+
cleanup();
137+
const value =
138+
(plainText.length > 0 ? parseValueFromText(plainText) : null) ??
139+
parseValueFromElement(dummyInput);
113140
if (value != null) {
114141
grid.pasteValue(value);
115142
}
116143
};
117144

118-
// Listen for the `input` event, when there's a change to the HTML
119-
// We could also listen to the `paste` event to get the clipboard data, but that's just text data
120-
// By listening to `input`, we can get a table that's already parsed in HTML, which is easier to consume
121-
dummyInput.addEventListener('input', listener);
145+
dummyInput.addEventListener('paste', pasteListener);
146+
dummyInput.addEventListener('input', inputListener);
122147

123148
// Focus the element so it receives the paste event
124149
dummyInput.focus();

0 commit comments

Comments
 (0)