@shipstatic/drop 2.0.0-beta.4 → 2.0.0-beta.7
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/README.md +17 -7
- package/dist/index.cjs +110 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +110 -10
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +98 -4
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +98 -4
- package/dist/testing.js.map +1 -1
- package/dist/{useDrop-Bmjfjp_o.d.cts → useDrop-C43AHhf8.d.cts} +45 -14
- package/dist/{useDrop-Bmjfjp_o.d.ts → useDrop-C43AHhf8.d.ts} +45 -14
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -94,10 +94,10 @@ interface DropReturn {
|
|
|
94
94
|
|
|
95
95
|
// Prop getters
|
|
96
96
|
getDropzoneProps: (options?: { clickable?: boolean }) => { ... };
|
|
97
|
-
getInputProps: () => { ... };
|
|
97
|
+
getInputProps: (mode?: PickerMode) => { ... }; // 'folder' (default) | 'files'
|
|
98
98
|
|
|
99
99
|
// Actions
|
|
100
|
-
open: () => void;
|
|
100
|
+
open: (mode?: PickerMode) => void; // trigger a picker (default: folder)
|
|
101
101
|
processFiles: (files: File[]) => Promise<void>; // advanced — see below
|
|
102
102
|
reset: () => void;
|
|
103
103
|
|
|
@@ -141,18 +141,28 @@ useEffect(() => {
|
|
|
141
141
|
</div>
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
-
Drag-only, with your own
|
|
144
|
+
Drag-only, with your own triggers:
|
|
145
145
|
|
|
146
146
|
```tsx
|
|
147
147
|
<div {...drop.getDropzoneProps({ clickable: false })}>
|
|
148
|
-
<input {...drop.getInputProps()} />
|
|
149
|
-
<
|
|
148
|
+
<input {...drop.getInputProps('folder')} />
|
|
149
|
+
<input {...drop.getInputProps('files')} />
|
|
150
|
+
<button onClick={() => drop.open('folder')}>Select folder</button>
|
|
151
|
+
<button onClick={() => drop.open('files')}>Select files</button>
|
|
150
152
|
</div>
|
|
151
153
|
```
|
|
152
154
|
|
|
153
155
|
`getDropzoneProps()` handles `webkitGetAsEntry` internally, which is what preserves folder structure. Calling `processFiles()` yourself loses it — the browser invalidates `dataTransfer.items` at the first `await`, so entries must be captured synchronously.
|
|
154
156
|
|
|
155
|
-
|
|
157
|
+
### Two pickers
|
|
158
|
+
|
|
159
|
+
`PickerMode` is `'folder' | 'files'`, and **folder is the default** — a bare `getInputProps()` / `open()`, and the dropzone's own click, open the folder picker.
|
|
160
|
+
|
|
161
|
+
An `<input>` is either a folder picker or a file picker, so each mode owns its own element and its own ref: a UI offering both renders **both inputs**, and `open(mode)` clicks whichever is mounted. Exactly one attribute differs — `webkitdirectory` in folder mode, `accept` in files mode.
|
|
162
|
+
|
|
163
|
+
Wrap `open` in a handler rather than passing it by reference (`onClick={() => drop.open('files')}`): React hands a click handler a `MouseEvent`, which would otherwise arrive as the mode.
|
|
164
|
+
|
|
165
|
+
**Selecting is not a second code path.** A picked file set — loose files or a ZIP — runs the identical pipeline as a dropped one, with the same paths, the same source name and the same verdict. The `accept` list is a *hint* that biases what the file dialog shows first; it decides nothing, since every dialog offers an all-files escape and drag & drop ignores `accept` outright. What files may be deployed is one rule, applied downstream of both entry points.
|
|
156
166
|
|
|
157
167
|
## Validation
|
|
158
168
|
|
|
@@ -191,7 +201,7 @@ await ship.deployments.upload(drop.getFilesForUpload(), {
|
|
|
191
201
|
|
|
192
202
|
## ZIP handling
|
|
193
203
|
|
|
194
|
-
A **single** dropped ZIP is extracted and its contents deployed. ZIPs among several files are treated as ordinary files. Archive paths are sanitized against directory traversal (`../../
|
|
204
|
+
A **single** dropped ZIP is extracted and its contents deployed. ZIPs among several files are treated as ordinary files. Archive paths are sanitized against directory traversal (`../../config.json` → `config.json`).
|
|
195
205
|
|
|
196
206
|
## Without React
|
|
197
207
|
|
package/dist/index.cjs
CHANGED
|
@@ -4,10 +4,16 @@ var ship = require('@shipstatic/ship');
|
|
|
4
4
|
var react = require('react');
|
|
5
5
|
|
|
6
6
|
// src/process.ts
|
|
7
|
-
|
|
8
|
-
// node_modules/.pnpm/@shipstatic+types@2.2.1-beta.0/node_modules/@shipstatic/types/dist/index.js
|
|
9
7
|
var ErrorType = {
|
|
10
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Validation failed. Input shape is wrong.
|
|
10
|
+
*
|
|
11
|
+
* Carries 400 when an API judged it — including a client-side pre-check of a
|
|
12
|
+
* rule the server enforces too, which keeps the error identical wherever it
|
|
13
|
+
* was caught. **Statusless** when a client rejects something no API judges,
|
|
14
|
+
* such as a CLI's own command grammar: `status` is documented "(API
|
|
15
|
+
* contexts)" on `ErrorResponse`, so there is none to report.
|
|
16
|
+
*/
|
|
11
17
|
Validation: "validation_failed",
|
|
12
18
|
/** Resource not found (404). */
|
|
13
19
|
NotFound: "not_found",
|
|
@@ -40,6 +46,90 @@ new Set(Object.values(ErrorType).filter((t) => !CLIENT_ONLY_ERROR_TYPES.has(t)))
|
|
|
40
46
|
function isShipError(error) {
|
|
41
47
|
return error !== null && typeof error === "object" && "name" in error && error.name === "ShipError" && "status" in error;
|
|
42
48
|
}
|
|
49
|
+
var WEB_FILE_EXTENSIONS = [
|
|
50
|
+
// Markup & documents
|
|
51
|
+
"html",
|
|
52
|
+
"htm",
|
|
53
|
+
"xhtml",
|
|
54
|
+
"xml",
|
|
55
|
+
"txt",
|
|
56
|
+
"md",
|
|
57
|
+
"markdown",
|
|
58
|
+
"pdf",
|
|
59
|
+
"csv",
|
|
60
|
+
// Data & config
|
|
61
|
+
"json",
|
|
62
|
+
"jsonc",
|
|
63
|
+
"webmanifest",
|
|
64
|
+
"map",
|
|
65
|
+
"toml",
|
|
66
|
+
"yaml",
|
|
67
|
+
"yml",
|
|
68
|
+
"rss",
|
|
69
|
+
"atom",
|
|
70
|
+
// Styles
|
|
71
|
+
"css",
|
|
72
|
+
"scss",
|
|
73
|
+
"sass",
|
|
74
|
+
"less",
|
|
75
|
+
// Scripts & modules
|
|
76
|
+
"js",
|
|
77
|
+
"mjs",
|
|
78
|
+
"cjs",
|
|
79
|
+
"jsx",
|
|
80
|
+
"ts",
|
|
81
|
+
"tsx",
|
|
82
|
+
"wasm",
|
|
83
|
+
"vue",
|
|
84
|
+
"svelte",
|
|
85
|
+
// Images
|
|
86
|
+
"png",
|
|
87
|
+
"jpg",
|
|
88
|
+
"jpeg",
|
|
89
|
+
"gif",
|
|
90
|
+
"webp",
|
|
91
|
+
"avif",
|
|
92
|
+
"svg",
|
|
93
|
+
"ico",
|
|
94
|
+
"bmp",
|
|
95
|
+
"tif",
|
|
96
|
+
"tiff",
|
|
97
|
+
"heic",
|
|
98
|
+
"heif",
|
|
99
|
+
// Fonts
|
|
100
|
+
"woff",
|
|
101
|
+
"woff2",
|
|
102
|
+
"ttf",
|
|
103
|
+
"otf",
|
|
104
|
+
"eot",
|
|
105
|
+
// Audio
|
|
106
|
+
"mp3",
|
|
107
|
+
"wav",
|
|
108
|
+
"ogg",
|
|
109
|
+
"oga",
|
|
110
|
+
"opus",
|
|
111
|
+
"m4a",
|
|
112
|
+
"aac",
|
|
113
|
+
"flac",
|
|
114
|
+
"weba",
|
|
115
|
+
// Video
|
|
116
|
+
"mp4",
|
|
117
|
+
"webm",
|
|
118
|
+
"ogv",
|
|
119
|
+
"mov",
|
|
120
|
+
"m4v",
|
|
121
|
+
"avi",
|
|
122
|
+
// 3D models
|
|
123
|
+
"glb",
|
|
124
|
+
"gltf",
|
|
125
|
+
"usdz",
|
|
126
|
+
// Text tracks
|
|
127
|
+
"vtt",
|
|
128
|
+
"srt",
|
|
129
|
+
// Archive — a whole site in one file
|
|
130
|
+
"zip"
|
|
131
|
+
];
|
|
132
|
+
var WEB_FILE_ACCEPT = WEB_FILE_EXTENSIONS.map((ext) => `.${ext}`).join(",");
|
|
43
133
|
var UNBUILT_PROJECT_MARKERS = /* @__PURE__ */ new Set([
|
|
44
134
|
"node_modules",
|
|
45
135
|
"package.json"
|
|
@@ -948,7 +1038,8 @@ var initialState = {
|
|
|
948
1038
|
function useDrop({ ship }) {
|
|
949
1039
|
const [state, setState] = react.useState(initialState);
|
|
950
1040
|
const isProcessingRef = react.useRef(false);
|
|
951
|
-
const
|
|
1041
|
+
const folderInputRef = react.useRef(null);
|
|
1042
|
+
const filesInputRef = react.useRef(null);
|
|
952
1043
|
const isProcessing = state.phase === "processing";
|
|
953
1044
|
const hasError = state.phase === "error";
|
|
954
1045
|
const isInteractive = state.phase === "idle" || state.phase === "ready";
|
|
@@ -1042,8 +1133,15 @@ function useDrop({ ship }) {
|
|
|
1042
1133
|
},
|
|
1043
1134
|
[processFiles2]
|
|
1044
1135
|
);
|
|
1045
|
-
const open = react.useCallback(() => {
|
|
1046
|
-
|
|
1136
|
+
const open = react.useCallback((mode) => {
|
|
1137
|
+
const input = mode === "files" ? filesInputRef.current : folderInputRef.current;
|
|
1138
|
+
if (!input) {
|
|
1139
|
+
console.warn(
|
|
1140
|
+
`No ${mode === "files" ? "files" : "folder"} input is mounted. Spread getInputProps('${mode === "files" ? "files" : "folder"}') onto an <input> to open this picker.`
|
|
1141
|
+
);
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
input.click();
|
|
1047
1145
|
}, []);
|
|
1048
1146
|
const getDropzoneProps = react.useCallback(
|
|
1049
1147
|
(options) => {
|
|
@@ -1052,18 +1150,20 @@ function useDrop({ ship }) {
|
|
|
1052
1150
|
onDragOver: handleDragOver,
|
|
1053
1151
|
onDragLeave: handleDragLeave,
|
|
1054
1152
|
onDrop: handleDrop,
|
|
1055
|
-
|
|
1153
|
+
// Wrapped rather than passed by reference: `open` takes a mode, and a
|
|
1154
|
+
// click handler would hand it a MouseEvent.
|
|
1155
|
+
...clickable && { onClick: () => open() }
|
|
1056
1156
|
};
|
|
1057
1157
|
},
|
|
1058
1158
|
[handleDragOver, handleDragLeave, handleDrop, open]
|
|
1059
1159
|
);
|
|
1060
1160
|
const getInputProps = react.useCallback(
|
|
1061
|
-
() => ({
|
|
1062
|
-
ref:
|
|
1161
|
+
(mode) => ({
|
|
1162
|
+
ref: mode === "files" ? filesInputRef : folderInputRef,
|
|
1063
1163
|
type: "file",
|
|
1064
1164
|
style: { display: "none" },
|
|
1065
1165
|
multiple: true,
|
|
1066
|
-
webkitdirectory: "",
|
|
1166
|
+
...mode === "files" ? { accept: WEB_FILE_ACCEPT } : { webkitdirectory: "" },
|
|
1067
1167
|
onChange: handleInputChange
|
|
1068
1168
|
}),
|
|
1069
1169
|
[handleInputChange]
|