@gradio/upload 0.15.7 → 0.16.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @gradio/upload
2
2
 
3
+ ## 0.16.0
4
+
5
+ ### Features
6
+
7
+ - [#10635](https://github.com/gradio-app/gradio/pull/10635) [`2f68c9d`](https://github.com/gradio-app/gradio/commit/2f68c9d988dcbc53a0b8e53bdb1de49c9c8c65d8) - Refactor and redesign `ImageEditor` component. Thanks @pngwn!
8
+
9
+ ### Dependency updates
10
+
11
+ - @gradio/atoms@0.15.0
12
+ - @gradio/icons@0.11.0
13
+
3
14
  ## 0.15.7
4
15
 
5
16
  ### Dependency updates
@@ -2,6 +2,8 @@
2
2
  import { prepare_files } from "@gradio/client";
3
3
  import { _ } from "svelte-i18n";
4
4
  import UploadProgress from "./UploadProgress.svelte";
5
+ import { create_drag } from "./utils";
6
+ const { drag, open_file_upload: _open_file_upload } = create_drag();
5
7
  export let filetype = null;
6
8
  export let dragging = false;
7
9
  export let boundedheight = true;
@@ -13,7 +15,6 @@ export let root;
13
15
  export let hidden = false;
14
16
  export let format = "file";
15
17
  export let uploading = false;
16
- export let hidden_upload = null;
17
18
  export let show_progress = true;
18
19
  export let max_file_size = null;
19
20
  export let upload;
@@ -21,6 +22,9 @@ export let stream_handler;
21
22
  export let icon_upload = false;
22
23
  export let height = void 0;
23
24
  export let aria_label = void 0;
25
+ export function open_upload() {
26
+ _open_file_upload();
27
+ }
24
28
  let upload_id;
25
29
  let file_data;
26
30
  let accept_file_types;
@@ -63,9 +67,6 @@ $:
63
67
  filetype = filetype.map(process_file_type);
64
68
  accept_file_types = filetype.join(", ");
65
69
  }
66
- function updateDragging() {
67
- dragging = !dragging;
68
- }
69
70
  export function paste_clipboard() {
70
71
  navigator.clipboard.read().then(async (items) => {
71
72
  for (let i = 0; i < items.length; i++) {
@@ -84,12 +85,7 @@ export function paste_clipboard() {
84
85
  });
85
86
  }
86
87
  export function open_file_upload() {
87
- if (disable_click)
88
- return;
89
- if (hidden_upload) {
90
- hidden_upload.value = "";
91
- hidden_upload.click();
92
- }
88
+ _open_file_upload();
93
89
  }
94
90
  async function handle_upload(file_data2) {
95
91
  await tick();
@@ -111,6 +107,23 @@ async function handle_upload(file_data2) {
111
107
  return [];
112
108
  }
113
109
  }
110
+ function is_valid_mimetype(file_accept, uploaded_file_extension, uploaded_file_type) {
111
+ if (!file_accept || file_accept === "*" || file_accept === "file/*" || Array.isArray(file_accept) && file_accept.some((accept) => accept === "*" || accept === "file/*")) {
112
+ return true;
113
+ }
114
+ let acceptArray;
115
+ if (typeof file_accept === "string") {
116
+ acceptArray = file_accept.split(",").map((s) => s.trim());
117
+ } else if (Array.isArray(file_accept)) {
118
+ acceptArray = file_accept;
119
+ } else {
120
+ return false;
121
+ }
122
+ return acceptArray.includes(uploaded_file_extension) || acceptArray.some((type) => {
123
+ const [category] = type.split("/").map((s) => s.trim());
124
+ return type.endsWith("/*") && uploaded_file_type.startsWith(category + "/");
125
+ });
126
+ }
114
127
  export async function load_files(files) {
115
128
  if (!files.length) {
116
129
  return;
@@ -155,42 +168,8 @@ function is_valid_file(file) {
155
168
  return file.type === processed_type;
156
169
  });
157
170
  }
158
- async function load_files_from_upload(e) {
159
- const target = e.target;
160
- if (!target.files)
161
- return;
162
- if (format != "blob") {
163
- await load_files(Array.from(target.files));
164
- } else {
165
- if (file_count === "single") {
166
- dispatch("load", target.files[0]);
167
- return;
168
- }
169
- dispatch("load", target.files);
170
- }
171
- }
172
- function is_valid_mimetype(file_accept, uploaded_file_extension, uploaded_file_type) {
173
- if (!file_accept || file_accept === "*" || file_accept === "file/*" || Array.isArray(file_accept) && file_accept.some((accept) => accept === "*" || accept === "file/*")) {
174
- return true;
175
- }
176
- let acceptArray;
177
- if (typeof file_accept === "string") {
178
- acceptArray = file_accept.split(",").map((s) => s.trim());
179
- } else if (Array.isArray(file_accept)) {
180
- acceptArray = file_accept;
181
- } else {
182
- return false;
183
- }
184
- return acceptArray.includes(uploaded_file_extension) || acceptArray.some((type) => {
185
- const [category] = type.split("/").map((s) => s.trim());
186
- return type.endsWith("/*") && uploaded_file_type.startsWith(category + "/");
187
- });
188
- }
189
- async function loadFilesFromDrop(e) {
190
- dragging = false;
191
- if (!e.dataTransfer?.files)
192
- return;
193
- const files_to_load = Array.from(e.dataTransfer.files).filter((file) => {
171
+ async function load_files_from_upload(files) {
172
+ const files_to_load = files.filter((file) => {
194
173
  const file_extension = "." + file.name.split(".").pop();
195
174
  if (file_extension && is_valid_mimetype(accept_file_types, file_extension, file.type)) {
196
175
  return true;
@@ -253,32 +232,17 @@ async function loadFilesFromDrop(e) {
253
232
  : height
254
233
  : "100%"}
255
234
  tabindex={hidden ? -1 : 0}
256
- on:drag|preventDefault|stopPropagation
257
- on:dragstart|preventDefault|stopPropagation
258
- on:dragend|preventDefault|stopPropagation
259
- on:dragover|preventDefault|stopPropagation
260
- on:dragenter|preventDefault|stopPropagation
261
- on:dragleave|preventDefault|stopPropagation
262
- on:drop|preventDefault|stopPropagation
263
- on:click={open_file_upload}
264
- on:drop={loadFilesFromDrop}
265
- on:dragenter={updateDragging}
266
- on:dragleave={updateDragging}
235
+ use:drag={{
236
+ on_drag_change: (dragging) => (dragging = dragging),
237
+ on_files: (files) => load_files_from_upload(files),
238
+ accepted_types: accept_file_types,
239
+ mode: file_count,
240
+ disable_click
241
+ }}
267
242
  aria-label={aria_label || "Click to upload or drop files"}
268
243
  aria-dropeffect="copy"
269
244
  >
270
245
  <slot />
271
- <input
272
- aria-label="File upload"
273
- data-testid="file-upload"
274
- type="file"
275
- bind:this={hidden_upload}
276
- on:change={load_files_from_upload}
277
- accept={accept_file_types || undefined}
278
- multiple={file_count === "multiple" || undefined}
279
- webkitdirectory={file_count === "directory" || undefined}
280
- mozdirectory={file_count === "directory" || undefined}
281
- />
282
246
  </button>
283
247
  {/if}
284
248
 
@@ -312,10 +276,6 @@ async function loadFilesFromDrop(e) {
312
276
  cursor: default;
313
277
  }
314
278
 
315
- input {
316
- display: none;
317
- }
318
-
319
279
  .icon-mode {
320
280
  position: absolute !important;
321
281
  width: var(--size-4);
@@ -14,7 +14,6 @@ declare const __propDef: {
14
14
  hidden?: boolean | undefined;
15
15
  format?: ("blob" | "file") | undefined;
16
16
  uploading?: boolean | undefined;
17
- hidden_upload?: (HTMLInputElement | null) | undefined;
18
17
  show_progress?: boolean | undefined;
19
18
  max_file_size?: (number | null) | undefined;
20
19
  upload: Client["upload"];
@@ -22,18 +21,12 @@ declare const __propDef: {
22
21
  icon_upload?: boolean | undefined;
23
22
  height?: number | string | undefined;
24
23
  aria_label?: string | undefined;
24
+ open_upload?: (() => void) | undefined;
25
25
  paste_clipboard?: (() => void) | undefined;
26
26
  open_file_upload?: (() => void) | undefined;
27
27
  load_files?: ((files: File[] | Blob[]) => Promise<(FileData | null)[] | void>) | undefined;
28
28
  };
29
29
  events: {
30
- drag: DragEvent;
31
- dragstart: DragEvent;
32
- dragend: DragEvent;
33
- dragover: DragEvent;
34
- dragenter: DragEvent;
35
- dragleave: DragEvent;
36
- drop: DragEvent;
37
30
  load: CustomEvent<any>;
38
31
  error: CustomEvent<any>;
39
32
  } & {
@@ -47,6 +40,7 @@ export type UploadProps = typeof __propDef.props;
47
40
  export type UploadEvents = typeof __propDef.events;
48
41
  export type UploadSlots = typeof __propDef.slots;
49
42
  export default class Upload extends SvelteComponent<UploadProps, UploadEvents, UploadSlots> {
43
+ get open_upload(): () => void;
50
44
  get paste_clipboard(): () => void;
51
45
  get open_file_upload(): () => void;
52
46
  get load_files(): (files: File[] | Blob[]) => Promise<(FileData | null)[] | void>;
@@ -1,2 +1,3 @@
1
1
  export { default as Upload } from "./Upload.svelte";
2
2
  export { default as ModifyUpload } from "./ModifyUpload.svelte";
3
+ export { create_drag } from "./utils";
package/dist/src/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { default as Upload } from "./Upload.svelte";
2
2
  export { default as ModifyUpload } from "./ModifyUpload.svelte";
3
+ export { create_drag } from "./utils";
@@ -0,0 +1,16 @@
1
+ interface DragActionOptions {
2
+ disable_click?: boolean;
3
+ accepted_types?: string | string[] | null;
4
+ mode?: "single" | "multiple" | "directory";
5
+ on_drag_change?: (dragging: boolean) => void;
6
+ on_files?: (files: File[]) => void;
7
+ }
8
+ type ActionReturn = {
9
+ update: (new_options: DragActionOptions) => void;
10
+ destroy: () => void;
11
+ };
12
+ export declare function create_drag(): {
13
+ drag: (node: HTMLElement, options: DragActionOptions) => ActionReturn;
14
+ open_file_upload: () => void;
15
+ };
16
+ export {};
@@ -0,0 +1,107 @@
1
+ export function create_drag() {
2
+ let hidden_input;
3
+ let _options;
4
+ return {
5
+ drag(node, options = {}) {
6
+ _options = options;
7
+ // Create and configure hidden file input
8
+ function setup_hidden_input() {
9
+ hidden_input = document.createElement("input");
10
+ hidden_input.type = "file";
11
+ hidden_input.style.display = "none";
12
+ hidden_input.setAttribute("aria-label", "File upload");
13
+ hidden_input.setAttribute("data-testid", "file-upload");
14
+ const accept_options = Array.isArray(_options.accepted_types)
15
+ ? _options.accepted_types.join(",")
16
+ : _options.accepted_types || undefined;
17
+ if (accept_options) {
18
+ hidden_input.accept = accept_options;
19
+ }
20
+ hidden_input.multiple = _options.mode === "multiple" || false;
21
+ if (_options.mode === "directory") {
22
+ hidden_input.webkitdirectory = true;
23
+ hidden_input.setAttribute("directory", "");
24
+ hidden_input.setAttribute("mozdirectory", "");
25
+ }
26
+ node.appendChild(hidden_input);
27
+ }
28
+ setup_hidden_input();
29
+ function handle_drag(e) {
30
+ e.preventDefault();
31
+ e.stopPropagation();
32
+ }
33
+ function handle_drag_enter(e) {
34
+ e.preventDefault();
35
+ e.stopPropagation();
36
+ _options.on_drag_change?.(true);
37
+ }
38
+ function handle_drag_leave(e) {
39
+ e.preventDefault();
40
+ e.stopPropagation();
41
+ _options.on_drag_change?.(false);
42
+ }
43
+ function handle_drop(e) {
44
+ e.preventDefault();
45
+ e.stopPropagation();
46
+ _options.on_drag_change?.(false);
47
+ if (!e.dataTransfer?.files)
48
+ return;
49
+ const files = Array.from(e.dataTransfer.files);
50
+ if (files.length > 0) {
51
+ _options.on_files?.(files);
52
+ }
53
+ }
54
+ function handle_click() {
55
+ if (!_options.disable_click) {
56
+ hidden_input.value = "";
57
+ hidden_input.click();
58
+ }
59
+ }
60
+ function handle_file_input_change() {
61
+ if (hidden_input.files) {
62
+ const files = Array.from(hidden_input.files);
63
+ if (files.length > 0) {
64
+ _options.on_files?.(files);
65
+ }
66
+ }
67
+ }
68
+ // Add all event listeners
69
+ node.addEventListener("drag", handle_drag);
70
+ node.addEventListener("dragstart", handle_drag);
71
+ node.addEventListener("dragend", handle_drag);
72
+ node.addEventListener("dragover", handle_drag);
73
+ node.addEventListener("dragenter", handle_drag_enter);
74
+ node.addEventListener("dragleave", handle_drag_leave);
75
+ node.addEventListener("drop", handle_drop);
76
+ node.addEventListener("click", handle_click);
77
+ hidden_input.addEventListener("change", handle_file_input_change);
78
+ return {
79
+ update(new_options) {
80
+ _options = new_options;
81
+ // Recreate hidden input with new options
82
+ hidden_input.remove();
83
+ setup_hidden_input();
84
+ hidden_input.addEventListener("change", handle_file_input_change);
85
+ },
86
+ destroy() {
87
+ node.removeEventListener("drag", handle_drag);
88
+ node.removeEventListener("dragstart", handle_drag);
89
+ node.removeEventListener("dragend", handle_drag);
90
+ node.removeEventListener("dragover", handle_drag);
91
+ node.removeEventListener("dragenter", handle_drag_enter);
92
+ node.removeEventListener("dragleave", handle_drag_leave);
93
+ node.removeEventListener("drop", handle_drop);
94
+ node.removeEventListener("click", handle_click);
95
+ hidden_input.removeEventListener("change", handle_file_input_change);
96
+ hidden_input.remove();
97
+ }
98
+ };
99
+ },
100
+ open_file_upload() {
101
+ if (hidden_input) {
102
+ hidden_input.value = "";
103
+ hidden_input.click();
104
+ }
105
+ }
106
+ };
107
+ }
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@gradio/upload",
3
- "version": "0.15.7",
3
+ "version": "0.16.0",
4
4
  "description": "Gradio UI packages",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
7
7
  "author": "",
8
8
  "license": "ISC",
9
9
  "dependencies": {
10
- "@gradio/icons": "^0.10.0",
11
- "@gradio/atoms": "^0.14.1",
12
- "@gradio/client": "^1.14.1",
10
+ "@gradio/icons": "^0.11.0",
13
11
  "@gradio/wasm": "^0.18.1",
14
- "@gradio/utils": "^0.10.1"
12
+ "@gradio/utils": "^0.10.1",
13
+ "@gradio/client": "^1.14.1",
14
+ "@gradio/atoms": "^0.15.0"
15
15
  },
16
16
  "main_changeset": true,
17
17
  "exports": {
package/src/Upload.svelte CHANGED
@@ -4,6 +4,9 @@
4
4
  import { prepare_files, type Client } from "@gradio/client";
5
5
  import { _ } from "svelte-i18n";
6
6
  import UploadProgress from "./UploadProgress.svelte";
7
+ import { create_drag } from "./utils";
8
+
9
+ const { drag, open_file_upload: _open_file_upload } = create_drag();
7
10
 
8
11
  export let filetype: string | string[] | null = null;
9
12
  export let dragging = false;
@@ -16,7 +19,6 @@
16
19
  export let hidden = false;
17
20
  export let format: "blob" | "file" = "file";
18
21
  export let uploading = false;
19
- export let hidden_upload: HTMLInputElement | null = null;
20
22
  export let show_progress = true;
21
23
  export let max_file_size: number | null = null;
22
24
  export let upload: Client["upload"];
@@ -24,7 +26,9 @@
24
26
  export let icon_upload = false;
25
27
  export let height: number | string | undefined = undefined;
26
28
  export let aria_label: string | undefined = undefined;
27
-
29
+ export function open_upload(): void {
30
+ _open_file_upload();
31
+ }
28
32
  let upload_id: string;
29
33
  let file_data: FileData[];
30
34
  let accept_file_types: string | null;
@@ -70,10 +74,6 @@
70
74
  accept_file_types = filetype.join(", ");
71
75
  }
72
76
 
73
- function updateDragging(): void {
74
- dragging = !dragging;
75
- }
76
-
77
77
  export function paste_clipboard(): void {
78
78
  navigator.clipboard.read().then(async (items) => {
79
79
  for (let i = 0; i < items.length; i++) {
@@ -93,11 +93,7 @@
93
93
  }
94
94
 
95
95
  export function open_file_upload(): void {
96
- if (disable_click) return;
97
- if (hidden_upload) {
98
- hidden_upload.value = "";
99
- hidden_upload.click();
100
- }
96
+ _open_file_upload();
101
97
  }
102
98
 
103
99
  async function handle_upload(
@@ -123,6 +119,40 @@
123
119
  }
124
120
  }
125
121
 
122
+ function is_valid_mimetype(
123
+ file_accept: string | string[] | null,
124
+ uploaded_file_extension: string,
125
+ uploaded_file_type: string
126
+ ): boolean {
127
+ if (
128
+ !file_accept ||
129
+ file_accept === "*" ||
130
+ file_accept === "file/*" ||
131
+ (Array.isArray(file_accept) &&
132
+ file_accept.some((accept) => accept === "*" || accept === "file/*"))
133
+ ) {
134
+ return true;
135
+ }
136
+ let acceptArray: string[];
137
+ if (typeof file_accept === "string") {
138
+ acceptArray = file_accept.split(",").map((s) => s.trim());
139
+ } else if (Array.isArray(file_accept)) {
140
+ acceptArray = file_accept;
141
+ } else {
142
+ return false;
143
+ }
144
+
145
+ return (
146
+ acceptArray.includes(uploaded_file_extension) ||
147
+ acceptArray.some((type) => {
148
+ const [category] = type.split("/").map((s) => s.trim());
149
+ return (
150
+ type.endsWith("/*") && uploaded_file_type.startsWith(category + "/")
151
+ );
152
+ })
153
+ );
154
+ }
155
+
126
156
  export async function load_files(
127
157
  files: File[] | Blob[]
128
158
  ): Promise<(FileData | null)[] | void> {
@@ -180,58 +210,8 @@
180
210
  });
181
211
  }
182
212
 
183
- async function load_files_from_upload(e: Event): Promise<void> {
184
- const target = e.target as HTMLInputElement;
185
- if (!target.files) return;
186
- if (format != "blob") {
187
- await load_files(Array.from(target.files));
188
- } else {
189
- if (file_count === "single") {
190
- dispatch("load", target.files[0]);
191
- return;
192
- }
193
- dispatch("load", target.files);
194
- }
195
- }
196
-
197
- function is_valid_mimetype(
198
- file_accept: string | string[] | null,
199
- uploaded_file_extension: string,
200
- uploaded_file_type: string
201
- ): boolean {
202
- if (
203
- !file_accept ||
204
- file_accept === "*" ||
205
- file_accept === "file/*" ||
206
- (Array.isArray(file_accept) &&
207
- file_accept.some((accept) => accept === "*" || accept === "file/*"))
208
- ) {
209
- return true;
210
- }
211
- let acceptArray: string[];
212
- if (typeof file_accept === "string") {
213
- acceptArray = file_accept.split(",").map((s) => s.trim());
214
- } else if (Array.isArray(file_accept)) {
215
- acceptArray = file_accept;
216
- } else {
217
- return false;
218
- }
219
-
220
- return (
221
- acceptArray.includes(uploaded_file_extension) ||
222
- acceptArray.some((type) => {
223
- const [category] = type.split("/").map((s) => s.trim());
224
- return (
225
- type.endsWith("/*") && uploaded_file_type.startsWith(category + "/")
226
- );
227
- })
228
- );
229
- }
230
-
231
- async function loadFilesFromDrop(e: DragEvent): Promise<void> {
232
- dragging = false;
233
- if (!e.dataTransfer?.files) return;
234
- const files_to_load = Array.from(e.dataTransfer.files).filter((file) => {
213
+ async function load_files_from_upload(files: File[]): Promise<void> {
214
+ const files_to_load = files.filter((file) => {
235
215
  const file_extension = "." + file.name.split(".").pop();
236
216
  if (
237
217
  file_extension &&
@@ -302,32 +282,17 @@
302
282
  : height
303
283
  : "100%"}
304
284
  tabindex={hidden ? -1 : 0}
305
- on:drag|preventDefault|stopPropagation
306
- on:dragstart|preventDefault|stopPropagation
307
- on:dragend|preventDefault|stopPropagation
308
- on:dragover|preventDefault|stopPropagation
309
- on:dragenter|preventDefault|stopPropagation
310
- on:dragleave|preventDefault|stopPropagation
311
- on:drop|preventDefault|stopPropagation
312
- on:click={open_file_upload}
313
- on:drop={loadFilesFromDrop}
314
- on:dragenter={updateDragging}
315
- on:dragleave={updateDragging}
285
+ use:drag={{
286
+ on_drag_change: (dragging) => (dragging = dragging),
287
+ on_files: (files) => load_files_from_upload(files),
288
+ accepted_types: accept_file_types,
289
+ mode: file_count,
290
+ disable_click
291
+ }}
316
292
  aria-label={aria_label || "Click to upload or drop files"}
317
293
  aria-dropeffect="copy"
318
294
  >
319
295
  <slot />
320
- <input
321
- aria-label="File upload"
322
- data-testid="file-upload"
323
- type="file"
324
- bind:this={hidden_upload}
325
- on:change={load_files_from_upload}
326
- accept={accept_file_types || undefined}
327
- multiple={file_count === "multiple" || undefined}
328
- webkitdirectory={file_count === "directory" || undefined}
329
- mozdirectory={file_count === "directory" || undefined}
330
- />
331
296
  </button>
332
297
  {/if}
333
298
 
@@ -361,10 +326,6 @@
361
326
  cursor: default;
362
327
  }
363
328
 
364
- input {
365
- display: none;
366
- }
367
-
368
329
  .icon-mode {
369
330
  position: absolute !important;
370
331
  width: var(--size-4);
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { default as Upload } from "./Upload.svelte";
2
2
  export { default as ModifyUpload } from "./ModifyUpload.svelte";
3
+ export { create_drag } from "./utils";
package/src/utils.ts ADDED
@@ -0,0 +1,141 @@
1
+ interface DragActionOptions {
2
+ disable_click?: boolean;
3
+ accepted_types?: string | string[] | null;
4
+ mode?: "single" | "multiple" | "directory";
5
+ on_drag_change?: (dragging: boolean) => void;
6
+ on_files?: (files: File[]) => void;
7
+ }
8
+
9
+ type ActionReturn = {
10
+ update: (new_options: DragActionOptions) => void;
11
+ destroy: () => void;
12
+ };
13
+
14
+ export function create_drag(): {
15
+ drag: (node: HTMLElement, options: DragActionOptions) => ActionReturn;
16
+ open_file_upload: () => void;
17
+ } {
18
+ let hidden_input: HTMLInputElement;
19
+ let _options: DragActionOptions;
20
+ return {
21
+ drag(
22
+ node: HTMLElement,
23
+ options: DragActionOptions = {}
24
+ ): {
25
+ update: (new_options: DragActionOptions) => void;
26
+ destroy: () => void;
27
+ } {
28
+ _options = options;
29
+
30
+ // Create and configure hidden file input
31
+ function setup_hidden_input(): void {
32
+ hidden_input = document.createElement("input");
33
+ hidden_input.type = "file";
34
+ hidden_input.style.display = "none";
35
+ hidden_input.setAttribute("aria-label", "File upload");
36
+ hidden_input.setAttribute("data-testid", "file-upload");
37
+ const accept_options = Array.isArray(_options.accepted_types)
38
+ ? _options.accepted_types.join(",")
39
+ : _options.accepted_types || undefined;
40
+
41
+ if (accept_options) {
42
+ hidden_input.accept = accept_options;
43
+ }
44
+
45
+ hidden_input.multiple = _options.mode === "multiple" || false;
46
+ if (_options.mode === "directory") {
47
+ hidden_input.webkitdirectory = true;
48
+ hidden_input.setAttribute("directory", "");
49
+ hidden_input.setAttribute("mozdirectory", "");
50
+ }
51
+ node.appendChild(hidden_input);
52
+ }
53
+
54
+ setup_hidden_input();
55
+
56
+ function handle_drag(e: DragEvent): void {
57
+ e.preventDefault();
58
+ e.stopPropagation();
59
+ }
60
+
61
+ function handle_drag_enter(e: DragEvent): void {
62
+ e.preventDefault();
63
+ e.stopPropagation();
64
+ _options.on_drag_change?.(true);
65
+ }
66
+
67
+ function handle_drag_leave(e: DragEvent): void {
68
+ e.preventDefault();
69
+ e.stopPropagation();
70
+ _options.on_drag_change?.(false);
71
+ }
72
+
73
+ function handle_drop(e: DragEvent): void {
74
+ e.preventDefault();
75
+ e.stopPropagation();
76
+ _options.on_drag_change?.(false);
77
+
78
+ if (!e.dataTransfer?.files) return;
79
+ const files = Array.from(e.dataTransfer.files);
80
+ if (files.length > 0) {
81
+ _options.on_files?.(files);
82
+ }
83
+ }
84
+
85
+ function handle_click(): void {
86
+ if (!_options.disable_click) {
87
+ hidden_input.value = "";
88
+ hidden_input.click();
89
+ }
90
+ }
91
+
92
+ function handle_file_input_change(): void {
93
+ if (hidden_input.files) {
94
+ const files = Array.from(hidden_input.files);
95
+ if (files.length > 0) {
96
+ _options.on_files?.(files);
97
+ }
98
+ }
99
+ }
100
+
101
+ // Add all event listeners
102
+ node.addEventListener("drag", handle_drag);
103
+ node.addEventListener("dragstart", handle_drag);
104
+ node.addEventListener("dragend", handle_drag);
105
+ node.addEventListener("dragover", handle_drag);
106
+ node.addEventListener("dragenter", handle_drag_enter);
107
+ node.addEventListener("dragleave", handle_drag_leave);
108
+ node.addEventListener("drop", handle_drop);
109
+ node.addEventListener("click", handle_click);
110
+ hidden_input!.addEventListener("change", handle_file_input_change);
111
+
112
+ return {
113
+ update(new_options: DragActionOptions) {
114
+ _options = new_options;
115
+ // Recreate hidden input with new options
116
+ hidden_input.remove();
117
+ setup_hidden_input();
118
+ hidden_input.addEventListener("change", handle_file_input_change);
119
+ },
120
+ destroy() {
121
+ node.removeEventListener("drag", handle_drag);
122
+ node.removeEventListener("dragstart", handle_drag);
123
+ node.removeEventListener("dragend", handle_drag);
124
+ node.removeEventListener("dragover", handle_drag);
125
+ node.removeEventListener("dragenter", handle_drag_enter);
126
+ node.removeEventListener("dragleave", handle_drag_leave);
127
+ node.removeEventListener("drop", handle_drop);
128
+ node.removeEventListener("click", handle_click);
129
+ hidden_input.removeEventListener("change", handle_file_input_change);
130
+ hidden_input.remove();
131
+ }
132
+ };
133
+ },
134
+ open_file_upload(): void {
135
+ if (hidden_input) {
136
+ hidden_input.value = "";
137
+ hidden_input.click();
138
+ }
139
+ }
140
+ };
141
+ }