2.8 KB103 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import {useState} from 'react';
9
10import './copyFromSelectedColumn.css';
11
12function copyFromSelectedColumn(e: React.ClipboardEvent<HTMLTableElement>) {
13 const clipboard = e.clipboardData;
14 const text = getSelectedColumnText();
15 if (!text) {
16 return;
17 }
18 e.preventDefault();
19 clipboard.setData('text/plain', text);
20}
21
22function getSelectedColumnText(numColumns = 4): string | undefined {
23 const sel = window.getSelection();
24 if (sel == null) {
25 return;
26 }
27 const range = sel.getRangeAt(0);
28 const doc = range.cloneContents();
29 const nodes = doc.querySelectorAll('tr');
30
31 if (nodes.length === 0) {
32 return doc.textContent ?? '';
33 }
34
35 let text = '';
36
37 // We know how many columns are in the table, so each tr should contain ${numColumns} tds.
38 // But the selection may not be aligned at the start or end:
39 // start drag here
40 // v
41 // CC DDD
42 // AAA BBB CCC DDD
43 // AAA BBB CCC DDD
44 // AAA B
45 // ^
46 // end drag here
47
48 // We can compute what side we started dragging from by comparing
49 // the first row's length to the expected length of 4:
50 const idx = numColumns - nodes[0].children.length;
51
52 nodes.forEach((tr, i) => {
53 // The first row will be missing columns before the dragged point,
54 // so idx will always be 0 for that column.
55 // The last row also has the wrong number of columns, but idx is still correct
56 // as long as it's within bounds.
57 const newIdx = i === 0 ? 0 : idx;
58 if (newIdx >= 0) {
59 const td = tr.cells[newIdx];
60 if (td) {
61 text += (i > 0 ? '\n' : '') + td.textContent;
62 }
63 }
64 });
65
66 return text;
67}
68
69/**
70 * When using a <table> to render columns of text, add support
71 * for selecting ( & copying) within any one column.
72 * Returns props to be forwarded to the table.
73 *
74 * Note: you need to add data-column={3} prop (replace 3 with appropriate column index) to your <td>s to get the
75 * selection to only appear in one column.
76 */
77export function useTableColumnSelection(): React.TableHTMLAttributes<HTMLTableElement> {
78 const [selectedColumn, setSelectedColumn] = useState(0);
79
80 return {
81 onCopy: copyFromSelectedColumn,
82 className: `single-column-selection-table selected-column-${selectedColumn}`,
83 onMouseDown: e => {
84 const td = findParent('TD', e.target as HTMLElement) as HTMLTableCellElement;
85 if (td) {
86 const col = td.cellIndex;
87 setSelectedColumn(col);
88 }
89 },
90 };
91}
92
93function findParent(type: string, start: HTMLElement): HTMLElement {
94 let el = start;
95 while (el.tagName !== type) {
96 if (el.parentElement == null) {
97 return el;
98 }
99 el = el.parentElement;
100 }
101 return el;
102}
103