@voila.dev/ui-spreadsheet 1.1.9
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/package.json +58 -0
- package/src/components/nested-table-input.tsx +85 -0
- package/src/components/spreadsheet.tsx +1288 -0
- package/src/hooks/use-spreadsheet-column-sizing.ts +120 -0
- package/src/hooks/use-spreadsheet-drag.ts +617 -0
- package/src/hooks/use-spreadsheet-grid.ts +743 -0
- package/src/hooks/use-spreadsheet-image-drop.ts +114 -0
- package/src/styles.css +10 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* First image file in a drop or clipboard payload, or null when it carries
|
|
5
|
+
* none. `DataTransferItemList` is the clipboard's only reliable file view in
|
|
6
|
+
* Safari, so both sources are read through `files` after normalizing.
|
|
7
|
+
*/
|
|
8
|
+
function firstImageFile(data: DataTransfer | null): File | null {
|
|
9
|
+
if (data === null) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
for (const file of Array.from(data.files)) {
|
|
13
|
+
if (file.type.startsWith("image/")) {
|
|
14
|
+
return file;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface SpreadsheetImageDropProps {
|
|
21
|
+
onDragEnter: React.DragEventHandler<HTMLTableCellElement>;
|
|
22
|
+
onDragOver: React.DragEventHandler<HTMLTableCellElement>;
|
|
23
|
+
onDragLeave: React.DragEventHandler<HTMLTableCellElement>;
|
|
24
|
+
onDrop: React.DragEventHandler<HTMLTableCellElement>;
|
|
25
|
+
onPaste: React.ClipboardEventHandler<HTMLTableCellElement>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The two pointer-free import gestures of an image cell: dropping a file onto
|
|
30
|
+
* it and pasting one from the clipboard (the file picker is a plain button, it
|
|
31
|
+
* needs no hook). Returns `dragging` for the drop highlight plus the props to
|
|
32
|
+
* spread on the cell.
|
|
33
|
+
*
|
|
34
|
+
* Both handlers stop propagation once they have consumed a file: the table's
|
|
35
|
+
* grid layer owns paste (TSV) and the rows own drag (reordering), and neither
|
|
36
|
+
* should also see an image landing on a cell. A payload without an image file
|
|
37
|
+
* is left to bubble untouched.
|
|
38
|
+
*
|
|
39
|
+
* `dragenter`/`dragleave` fire per descendant as the pointer crosses the
|
|
40
|
+
* thumbnail and the buttons inside the cell, so the highlight is driven by a
|
|
41
|
+
* depth counter rather than a boolean - a plain toggle flickers off the moment
|
|
42
|
+
* the pointer enters a child.
|
|
43
|
+
*/
|
|
44
|
+
function useSpreadsheetImageDrop({
|
|
45
|
+
onFileSelect,
|
|
46
|
+
disabled,
|
|
47
|
+
}: {
|
|
48
|
+
onFileSelect: (file: File) => void;
|
|
49
|
+
disabled: boolean;
|
|
50
|
+
}): { dragging: boolean; cellProps: SpreadsheetImageDropProps } {
|
|
51
|
+
const [dragging, setDragging] = React.useState(false);
|
|
52
|
+
const depth = React.useRef(0);
|
|
53
|
+
|
|
54
|
+
const reset = () => {
|
|
55
|
+
depth.current = 0;
|
|
56
|
+
setDragging(false);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
dragging,
|
|
61
|
+
cellProps: {
|
|
62
|
+
onDragEnter: (event) => {
|
|
63
|
+
if (disabled || firstImageFile(event.dataTransfer) === null) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
event.stopPropagation();
|
|
68
|
+
depth.current += 1;
|
|
69
|
+
setDragging(true);
|
|
70
|
+
},
|
|
71
|
+
onDragOver: (event) => {
|
|
72
|
+
if (disabled || firstImageFile(event.dataTransfer) === null) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// Without preventDefault on dragover the browser refuses the drop.
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
event.stopPropagation();
|
|
78
|
+
event.dataTransfer.dropEffect = "copy";
|
|
79
|
+
},
|
|
80
|
+
onDragLeave: (event) => {
|
|
81
|
+
if (depth.current === 0) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
event.stopPropagation();
|
|
85
|
+
depth.current -= 1;
|
|
86
|
+
if (depth.current === 0) {
|
|
87
|
+
setDragging(false);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
onDrop: (event) => {
|
|
91
|
+
const file = firstImageFile(event.dataTransfer);
|
|
92
|
+
if (disabled || file === null) {
|
|
93
|
+
reset();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
event.preventDefault();
|
|
97
|
+
event.stopPropagation();
|
|
98
|
+
reset();
|
|
99
|
+
onFileSelect(file);
|
|
100
|
+
},
|
|
101
|
+
onPaste: (event) => {
|
|
102
|
+
const file = firstImageFile(event.clipboardData);
|
|
103
|
+
if (disabled || file === null) {
|
|
104
|
+
return; // Text payload: the grid's TSV paste layer takes it.
|
|
105
|
+
}
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
event.stopPropagation();
|
|
108
|
+
onFileSelect(file);
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export { useSpreadsheetImageDrop };
|
package/src/styles.css
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tailwind must scan this package or none of its classes are generated — the
|
|
3
|
+
* components ship as .tsx source, not as compiled CSS.
|
|
4
|
+
*
|
|
5
|
+
* `@source` resolves relative to the file that declares it, so this works
|
|
6
|
+
* wherever the package ends up: node_modules in an app, a symlink in a
|
|
7
|
+
* workspace. Consumers import this file and add nothing of their own.
|
|
8
|
+
*/
|
|
9
|
+
@source "./";
|
|
10
|
+
@source not "./**/*.test.*";
|