|
4 | 4 | import { RadioGroup } from "bits-ui"; |
5 | 5 | import SingleFileInput from "$lib/components/files/SingleFileInput.svelte"; |
6 | 6 |
|
7 | | - let { label = "File", state = $bindable() }: MultimodalFileInputProps = $props(); |
| 7 | + let { state = $bindable(), label = "File", required = false }: MultimodalFileInputProps = $props(); |
8 | 8 |
|
9 | 9 | const instance = new MultimodalFileInputState({ |
10 | | - label: box.with(() => label), |
11 | 10 | state, |
| 11 | + label: box.with(() => label), |
| 12 | + required: box.with(() => required), |
12 | 13 | }); |
13 | 14 | state = instance; |
14 | 15 |
|
|
24 | 25 | event.preventDefault(); |
25 | 26 | } |
26 | 27 |
|
27 | | - async function handleFileDrop(event: DragEvent) { |
| 28 | + async function handleDrop(event: DragEvent) { |
28 | 29 | instance.dragActive = false; |
29 | 30 | event.preventDefault(); |
30 | | - const files = event.dataTransfer?.files; |
31 | | - if (!files || files.length !== 1) { |
| 31 | + if (!event.dataTransfer) { |
| 32 | + return; |
| 33 | + } |
| 34 | +
|
| 35 | + const types = event.dataTransfer.types; |
| 36 | + const files = event.dataTransfer.files; |
| 37 | +
|
| 38 | + // Handle file drops |
| 39 | + if (files.length > 1) { |
32 | 40 | alert("Only one file can be dropped at a time."); |
33 | 41 | return; |
| 42 | + } else if (files.length === 1) { |
| 43 | + instance.file = files[0]; |
| 44 | + instance.mode = "file"; |
| 45 | + return; |
| 46 | + } |
| 47 | +
|
| 48 | + // Handle URL drops |
| 49 | + if (types.includes("text/uri-list")) { |
| 50 | + const urls = event.dataTransfer |
| 51 | + .getData("text/uri-list") |
| 52 | + .split("\n") |
| 53 | + .filter((url) => url && !url.startsWith("#")); |
| 54 | + if (urls.length > 1) { |
| 55 | + alert("Only one URL can be dropped at a time."); |
| 56 | + return; |
| 57 | + } else if (urls.length === 1) { |
| 58 | + instance.url = urls[0]; |
| 59 | + instance.mode = "url"; |
| 60 | + return; |
| 61 | + } |
| 62 | + } |
| 63 | +
|
| 64 | + // Handle plain text drops |
| 65 | + if (types.includes("text/plain")) { |
| 66 | + const text = event.dataTransfer.getData("text/plain"); |
| 67 | + if (text) { |
| 68 | + instance.text = text; |
| 69 | + instance.mode = "text"; |
| 70 | + return; |
| 71 | + } |
34 | 72 | } |
35 | | - instance.file = files[0]; |
36 | | - instance.mode = "file"; |
37 | 73 | } |
38 | 74 | </script> |
39 | 75 |
|
|
52 | 88 | class="file-drop-target w-full" |
53 | 89 | data-drag-active={instance.dragActive} |
54 | 90 | ondragover={handleDragOver} |
55 | | - ondrop={handleFileDrop} |
| 91 | + ondrop={handleDrop} |
56 | 92 | ondragleavecapture={handleDragLeave} |
57 | 93 | > |
58 | 94 | <RadioGroup.Root class="mb-1 flex w-full gap-1" bind:value={instance.mode}> |
|
69 | 105 | {/if} |
70 | 106 | </div> |
71 | 107 |
|
| 108 | +<!-- TODO: Implement required prop for SingleFileInput --> |
72 | 109 | {#snippet fileInput()} |
73 | 110 | <SingleFileInput bind:file={instance.file} class="flex w-fit items-center gap-2 rounded-md border btn-ghost px-2 py-1 has-focus-visible:outline-2"> |
74 | 111 | <span class="iconify size-4 shrink-0 text-em-disabled octicon--file-16"></span> |
|
82 | 119 | {/snippet} |
83 | 120 |
|
84 | 121 | {#snippet urlInput()} |
85 | | - <input title="{label} URL" placeholder="Enter file URL" bind:value={instance.url} type="url" class="w-full rounded-md border px-2 py-1" /> |
| 122 | + <input title="{label} URL" bind:value={instance.url} placeholder="Enter file URL" type="url" {required} class="w-full rounded-md border px-2 py-1" /> |
86 | 123 | {/snippet} |
87 | 124 |
|
88 | 125 | {#snippet textInput()} |
89 | | - <textarea title="{label} Text" bind:value={instance.text} placeholder="Enter text here" class="w-full rounded-md border px-2 py-1"></textarea> |
| 126 | + <textarea title="{label} Text" bind:value={instance.text} placeholder="Enter text here" {required} class="w-full rounded-md border px-2 py-1"></textarea> |
90 | 127 | {/snippet} |
91 | 128 |
|
92 | 129 | <style> |
|
103 | 140 | align-items: center; |
104 | 141 | justify-content: center; |
105 | 142 |
|
106 | | - content: "Drop file here"; |
| 143 | + content: "Drop here"; |
107 | 144 | font-size: var(--text-3xl); |
108 | 145 | color: var(--color-black); |
109 | 146 |
|
|
0 commit comments