@functionalcms/svelte-components 4.12.7 → 4.13.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/dist/components/form/dropzone/DefaultDropzone.svelte +37 -0
- package/dist/components/form/dropzone/DefaultDropzone.svelte.d.ts +8 -0
- package/dist/components/form/dropzone/Dropzone.svelte +298 -0
- package/dist/components/form/dropzone/Dropzone.svelte.d.ts +4 -0
- package/dist/components/form/dropzone/UseDropzone.d.ts +3 -0
- package/dist/components/form/dropzone/UseDropzone.js +19 -0
- package/dist/components/form/dropzone/attr-accept.d.ts +12 -0
- package/dist/components/form/dropzone/attr-accept.js +29 -0
- package/dist/components/form/dropzone/default.d.ts +31 -0
- package/dist/components/form/dropzone/default.js +78 -0
- package/dist/components/form/dropzone/types.d.ts +60 -0
- package/dist/components/form/dropzone/types.js +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/package.json +3 -1
- package/dist/components/form/Dropzone.svelte +0 -184
- package/dist/components/form/Dropzone.svelte.d.ts +0 -4
- package/dist/components/form/dropzone.d.ts +0 -49
- package/dist/components/form/dropzone.js +0 -18
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { CustomDropzoneProps } from './types.ts';
|
|
4
|
+
interface DefaultDropzone extends CustomDropzoneProps {
|
|
5
|
+
defaultDropzoneElement: HTMLElement | undefined;
|
|
6
|
+
children: Snippet;
|
|
7
|
+
}
|
|
8
|
+
let { defaultDropzoneElement = $bindable(), children, ...props }: DefaultDropzone = $props();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<div bind:this={defaultDropzoneElement} class="dropzone" {...props}>
|
|
12
|
+
{@render children()}
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<style>
|
|
16
|
+
.dropzone {
|
|
17
|
+
flex: 1;
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
align-items: center;
|
|
21
|
+
padding: 20px;
|
|
22
|
+
border-width: 2px;
|
|
23
|
+
border-radius: 2px;
|
|
24
|
+
border-color: #eeeeee;
|
|
25
|
+
border-style: dashed;
|
|
26
|
+
background-color: #fafafa;
|
|
27
|
+
color: #bdbdbd;
|
|
28
|
+
outline: none;
|
|
29
|
+
transition: border 0.24s ease-in-out;
|
|
30
|
+
}
|
|
31
|
+
.dropzone:hover {
|
|
32
|
+
border-color: #2196f3;
|
|
33
|
+
}
|
|
34
|
+
.dropzone:focus {
|
|
35
|
+
border-color: #2196f3;
|
|
36
|
+
}
|
|
37
|
+
</style>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { CustomDropzoneProps } from './types.ts';
|
|
3
|
+
interface DefaultDropzone extends CustomDropzoneProps {
|
|
4
|
+
defaultDropzoneElement: HTMLElement | undefined;
|
|
5
|
+
children: Snippet;
|
|
6
|
+
}
|
|
7
|
+
declare const DefaultDropzone: import("svelte").Component<DefaultDropzone, {}, "defaultDropzoneElement">;
|
|
8
|
+
export default DefaultDropzone;
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { fromEvent } from 'file-selector';
|
|
3
|
+
import {
|
|
4
|
+
checkFiles,
|
|
5
|
+
generateErrorMessage,
|
|
6
|
+
isEventWithFiles,
|
|
7
|
+
isIeOrEdge,
|
|
8
|
+
isPropagationStopped
|
|
9
|
+
} from './default.js';
|
|
10
|
+
import type {
|
|
11
|
+
DropzoneEventHandler,
|
|
12
|
+
DropzoneProps,
|
|
13
|
+
FromEventFileTypes,
|
|
14
|
+
RejectedFile
|
|
15
|
+
} from './types.ts';
|
|
16
|
+
import type { EventHandler } from 'svelte/elements';
|
|
17
|
+
import DefaultDropzone from './DefaultDropzone.svelte';
|
|
18
|
+
import useDropzone from './UseDropzone.ts';
|
|
19
|
+
|
|
20
|
+
let {
|
|
21
|
+
accept,
|
|
22
|
+
disabled = false,
|
|
23
|
+
maxFileCountPerUpload = Infinity,
|
|
24
|
+
maxSize = Infinity,
|
|
25
|
+
minSize = 0,
|
|
26
|
+
multiple = false,
|
|
27
|
+
preventDropOnDocument = true,
|
|
28
|
+
disableDropzoneClick = false,
|
|
29
|
+
disableDropzoneKeydown = false,
|
|
30
|
+
disableDropzoneDrag = false,
|
|
31
|
+
name = '',
|
|
32
|
+
inputElement = $bindable(),
|
|
33
|
+
required = false,
|
|
34
|
+
dropzoneElement = $bindable(),
|
|
35
|
+
CustomDropzone,
|
|
36
|
+
children,
|
|
37
|
+
onDragenter,
|
|
38
|
+
onDragover,
|
|
39
|
+
onDragleave,
|
|
40
|
+
onDrop,
|
|
41
|
+
onFileDialogCancel
|
|
42
|
+
}: DropzoneProps = $props();
|
|
43
|
+
|
|
44
|
+
let isFileDialogActive: boolean = $state(false);
|
|
45
|
+
|
|
46
|
+
let defaultDropzoneElement: HTMLElement | undefined = $state();
|
|
47
|
+
|
|
48
|
+
let dropzoneRef: HTMLElement | undefined = $derived(dropzoneElement || defaultDropzoneElement);
|
|
49
|
+
|
|
50
|
+
let dragTargetsRef: EventTarget[] = $state([]);
|
|
51
|
+
async function getFileFromEvent<T extends FromEventFileTypes>(
|
|
52
|
+
event: Event
|
|
53
|
+
): Promise<{ acceptedFiles: T[]; rejectedFiles: RejectedFile<T>[] }> {
|
|
54
|
+
if (isPropagationStopped(event)) {
|
|
55
|
+
return { acceptedFiles: [], rejectedFiles: [] };
|
|
56
|
+
}
|
|
57
|
+
const files = (await fromEvent(event)) as T[];
|
|
58
|
+
const acceptedFiles: T[] = [];
|
|
59
|
+
const rejectedFiles: RejectedFile<T>[] = [];
|
|
60
|
+
|
|
61
|
+
files.forEach((file) => {
|
|
62
|
+
const { isAccepted, errors } = checkFiles({ file, accept, minSize, maxSize });
|
|
63
|
+
if (multiple && files.length > maxFileCountPerUpload) {
|
|
64
|
+
rejectedFiles.push({
|
|
65
|
+
file,
|
|
66
|
+
errors: [...errors, generateErrorMessage('TOO_MANY_FILES', { maxFileCountPerUpload })]
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!multiple && isAccepted && acceptedFiles.length > 0) {
|
|
71
|
+
rejectedFiles.push({
|
|
72
|
+
file,
|
|
73
|
+
errors: [generateErrorMessage('CANNOT_UPLOAD_MULTIPLE_FILES')]
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (isAccepted) {
|
|
78
|
+
acceptedFiles.push(file);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
rejectedFiles.push({ file, errors });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return { acceptedFiles, rejectedFiles };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function triggerEventWithFiles<T extends FromEventFileTypes>(
|
|
89
|
+
event: Event,
|
|
90
|
+
callbackToTrigger: DropzoneEventHandler<T> | undefined
|
|
91
|
+
) {
|
|
92
|
+
const files = await getFileFromEvent<T>(event);
|
|
93
|
+
if (!files) return { acceptedFiles: [], rejectedFiles: [] };
|
|
94
|
+
const { acceptedFiles, rejectedFiles } = files;
|
|
95
|
+
callbackToTrigger?.({ acceptedFiles, rejectedFiles, event });
|
|
96
|
+
|
|
97
|
+
return files;
|
|
98
|
+
}
|
|
99
|
+
// Fn for opening the file dialog programmatically
|
|
100
|
+
function openFileDialog() {
|
|
101
|
+
if (inputElement) {
|
|
102
|
+
isFileDialogActive = true;
|
|
103
|
+
inputElement.click();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// open the file dialog when SPACE/ENTER occurs on the dropzone
|
|
108
|
+
function onDropzoneKeyDown(event: KeyboardEvent) {
|
|
109
|
+
const target = event.target as HTMLElement | null;
|
|
110
|
+
const dropzoneElementType = target?.getAttribute('drozone-element-type');
|
|
111
|
+
// Ignore keyboard events bubbling up the DOM tree
|
|
112
|
+
if (target?.id !== 'dropzone-element' && dropzoneElementType === 'dropzone-element') {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (event.keyCode === 32 || event.keyCode === 13) {
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
openFileDialog();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// open the file dialog when click occurs on the dropzone
|
|
123
|
+
function onDropzoneClick() {
|
|
124
|
+
if (disableDropzoneClick) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// In IE11/Edge the file-browser dialog is blocking, therefore, use setTimeout()
|
|
129
|
+
// to ensure React can handle state changes
|
|
130
|
+
// See: https://github.com/react-dropzone/react-dropzone/issues/450
|
|
131
|
+
if (isIeOrEdge()) {
|
|
132
|
+
setTimeout(openFileDialog, 0);
|
|
133
|
+
} else {
|
|
134
|
+
openFileDialog();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const onDropzoneDragEnter: EventHandler<DragEvent> = async (event) => {
|
|
138
|
+
event.preventDefault();
|
|
139
|
+
|
|
140
|
+
const target = event.target;
|
|
141
|
+
if (target) dragTargetsRef = [...dragTargetsRef, target];
|
|
142
|
+
|
|
143
|
+
if (isEventWithFiles(event)) {
|
|
144
|
+
await triggerEventWithFiles(event, onDragenter);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const onDropzoneDragOver: EventHandler<DragEvent> = async (event) => {
|
|
149
|
+
event.preventDefault();
|
|
150
|
+
|
|
151
|
+
if (event.dataTransfer) {
|
|
152
|
+
try {
|
|
153
|
+
event.dataTransfer.dropEffect = 'copy';
|
|
154
|
+
} catch {} /* eslint-disable-line no-empty */
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (isEventWithFiles(event)) {
|
|
158
|
+
await triggerEventWithFiles(event, onDragover);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return false;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const onDropzoneDragLeave: EventHandler<DragEvent> = async (event) => {
|
|
165
|
+
event.preventDefault();
|
|
166
|
+
|
|
167
|
+
// Only deactivate once the dropzone and all children have been left
|
|
168
|
+
const targets = dragTargetsRef.filter(
|
|
169
|
+
(target) => dropzoneRef && dropzoneRef.contains(target as Node)
|
|
170
|
+
);
|
|
171
|
+
// Make sure to remove a target present multiple times only once
|
|
172
|
+
// (Firefox may fire dragenter/dragleave multiple times on the same element)
|
|
173
|
+
const target = event.target as HTMLElement;
|
|
174
|
+
const targetIdx = targets.indexOf(target);
|
|
175
|
+
if (targetIdx !== -1) {
|
|
176
|
+
targets.splice(targetIdx, 1);
|
|
177
|
+
}
|
|
178
|
+
dragTargetsRef = targets;
|
|
179
|
+
if (targets.length > 0) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (isEventWithFiles(event)) {
|
|
184
|
+
await triggerEventWithFiles(event, onDragleave);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const onDropzoneDrop = async (event: DragEvent | Event) => {
|
|
189
|
+
event.preventDefault();
|
|
190
|
+
isFileDialogActive = false;
|
|
191
|
+
dragTargetsRef = [];
|
|
192
|
+
if (isEventWithFiles(event)) {
|
|
193
|
+
const { acceptedFiles } = await triggerEventWithFiles(event, onDrop);
|
|
194
|
+
if ('dataTransfer' in event && event.dataTransfer && inputElement) {
|
|
195
|
+
const dataTransfer = new DataTransfer();
|
|
196
|
+
const incomingFiles = acceptedFiles;
|
|
197
|
+
incomingFiles.forEach((v) => dataTransfer.items.add(v));
|
|
198
|
+
inputElement.files = dataTransfer.files;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
event.stopPropagation();
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
let getHandler = $derived(<T extends Event>(fn: EventHandler<T>) => (disabled ? null : fn));
|
|
205
|
+
let getKeyboardEventHandle = $derived(<T extends Event>(fn: EventHandler<T>) =>
|
|
206
|
+
disableDropzoneKeydown ? null : getHandler(fn)
|
|
207
|
+
);
|
|
208
|
+
let getDragEventHandler = $derived((fn: EventHandler<DragEvent>) =>
|
|
209
|
+
disableDropzoneDrag ? null : getHandler(fn)
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
let defaultPlaceholderString = $derived(
|
|
213
|
+
multiple
|
|
214
|
+
? "Drag 'n' drop some files here, or click to select files"
|
|
215
|
+
: "Drag 'n' drop a file here, or click to select a file"
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
// allow the entire document to be a drag target
|
|
219
|
+
function onWindowDragOver(event: DragEvent) {
|
|
220
|
+
if (preventDropOnDocument) {
|
|
221
|
+
event.preventDefault();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function onWindowDrop(event: DragEvent) {
|
|
226
|
+
const target = event.target as HTMLElement;
|
|
227
|
+
if (!preventDropOnDocument) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (dropzoneRef?.contains(target)) {
|
|
231
|
+
// If we intercepted an event for our instance, let it propagate down to the instance's onDrop handler
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
event.preventDefault();
|
|
235
|
+
dragTargetsRef = [];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function onInputElementClick(event: MouseEvent) {
|
|
239
|
+
event.stopPropagation();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function onInputElementCancel(event: Event) {
|
|
243
|
+
isFileDialogActive = false;
|
|
244
|
+
onFileDialogCancel?.();
|
|
245
|
+
}
|
|
246
|
+
const dropzoneProps = $derived({
|
|
247
|
+
'data-drozone-element-type': 'dropzone-element',
|
|
248
|
+
id: 'dropzone-element',
|
|
249
|
+
tabindex: 0,
|
|
250
|
+
role: 'button',
|
|
251
|
+
onkeydown: getKeyboardEventHandle(onDropzoneKeyDown),
|
|
252
|
+
onclick: getHandler(onDropzoneClick)
|
|
253
|
+
});
|
|
254
|
+
const dropzoneAreaProps = $derived({
|
|
255
|
+
dragenter: getDragEventHandler(onDropzoneDragEnter),
|
|
256
|
+
dragover: getDragEventHandler(onDropzoneDragOver),
|
|
257
|
+
dragleave: getDragEventHandler(onDropzoneDragLeave),
|
|
258
|
+
drop: getDragEventHandler(onDropzoneDrop)
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
$effect(() => {
|
|
262
|
+
const unsubscribe = useDropzone(dropzoneRef, dropzoneAreaProps);
|
|
263
|
+
return () => unsubscribe();
|
|
264
|
+
});
|
|
265
|
+
</script>
|
|
266
|
+
|
|
267
|
+
<svelte:window on:dragover={onWindowDragOver} on:drop={onWindowDrop} />
|
|
268
|
+
|
|
269
|
+
{#snippet dropzoneInput()}
|
|
270
|
+
<input
|
|
271
|
+
accept={accept?.join(',')}
|
|
272
|
+
{multiple}
|
|
273
|
+
{required}
|
|
274
|
+
type="file"
|
|
275
|
+
{name}
|
|
276
|
+
autocomplete="off"
|
|
277
|
+
tabindex="-1"
|
|
278
|
+
onchange={onDropzoneDrop}
|
|
279
|
+
onclick={onInputElementClick}
|
|
280
|
+
bind:this={inputElement}
|
|
281
|
+
style="display: none;"
|
|
282
|
+
oncancel={onInputElementCancel}
|
|
283
|
+
/>
|
|
284
|
+
{/snippet}
|
|
285
|
+
{#if CustomDropzone}
|
|
286
|
+
{@render CustomDropzone(dropzoneProps)}
|
|
287
|
+
|
|
288
|
+
{@render dropzoneInput()}
|
|
289
|
+
{:else}
|
|
290
|
+
<DefaultDropzone bind:defaultDropzoneElement {...dropzoneProps}>
|
|
291
|
+
{@render dropzoneInput()}
|
|
292
|
+
{#if children}
|
|
293
|
+
{@render children()}
|
|
294
|
+
{:else}
|
|
295
|
+
<p>{defaultPlaceholderString}</p>
|
|
296
|
+
{/if}
|
|
297
|
+
</DefaultDropzone>
|
|
298
|
+
{/if}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const useDropzone = (element, events) => {
|
|
2
|
+
if (!element)
|
|
3
|
+
return () => { };
|
|
4
|
+
Object.entries(events)
|
|
5
|
+
.forEach(([eventName, handler]) => {
|
|
6
|
+
if (handler) {
|
|
7
|
+
element.addEventListener(eventName, handler);
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
return () => {
|
|
11
|
+
Object.entries(events)
|
|
12
|
+
.forEach(([eventName, handler]) => {
|
|
13
|
+
if (handler) {
|
|
14
|
+
element.removeEventListener(eventName, handler);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export default useDropzone;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if the provided file type should be accepted by the input with accept attribute.
|
|
3
|
+
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input#attr-accept
|
|
4
|
+
*
|
|
5
|
+
* Inspired by https://github.com/enyo/dropzone
|
|
6
|
+
*
|
|
7
|
+
* @param file {File} https://developer.mozilla.org/en-US/docs/Web/API/File
|
|
8
|
+
* @param accept {string}
|
|
9
|
+
* @returns {boolean}
|
|
10
|
+
*/
|
|
11
|
+
import type { FromEventFileTypes, MimeTypes } from "./types.ts";
|
|
12
|
+
export default function (file: FromEventFileTypes, accept: MimeTypes[] | string[]): boolean;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if the provided file type should be accepted by the input with accept attribute.
|
|
3
|
+
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input#attr-accept
|
|
4
|
+
*
|
|
5
|
+
* Inspired by https://github.com/enyo/dropzone
|
|
6
|
+
*
|
|
7
|
+
* @param file {File} https://developer.mozilla.org/en-US/docs/Web/API/File
|
|
8
|
+
* @param accept {string}
|
|
9
|
+
* @returns {boolean}
|
|
10
|
+
*/
|
|
11
|
+
export default function (file, accept) {
|
|
12
|
+
if (file && accept.length > 0) {
|
|
13
|
+
const fileName = "name" in file ? file.name : "";
|
|
14
|
+
const mimeType = (file.type || "").toLowerCase();
|
|
15
|
+
const baseMimeType = mimeType.replace(/\/.*$/, "");
|
|
16
|
+
return accept.some((type) => {
|
|
17
|
+
const validType = type.trim().toLowerCase();
|
|
18
|
+
if (validType.charAt(0) === ".") {
|
|
19
|
+
return fileName.toLowerCase().endsWith(validType);
|
|
20
|
+
}
|
|
21
|
+
else if (validType.endsWith("/*")) {
|
|
22
|
+
// This is something like a image/* mime type
|
|
23
|
+
return baseMimeType === validType.replace(/\/.*$/, "");
|
|
24
|
+
}
|
|
25
|
+
return mimeType === validType;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { FromEventFileTypes, MimeTypes, DropzoneErrorCode } from "./types.ts";
|
|
2
|
+
export declare const FILE_INVALID_TYPE = "file-invalid-type";
|
|
3
|
+
export declare const FILE_TOO_LARGE = "file-too-large";
|
|
4
|
+
export declare const FILE_TOO_SMALL = "file-too-small";
|
|
5
|
+
export declare const TOO_MANY_FILES = "too-many-files";
|
|
6
|
+
export declare const generateErrorMessage: (code: DropzoneErrorCode, props?: {
|
|
7
|
+
accept?: string[];
|
|
8
|
+
minSize?: number;
|
|
9
|
+
maxSize?: number;
|
|
10
|
+
maxFileCountPerUpload?: number;
|
|
11
|
+
}) => {
|
|
12
|
+
code: DropzoneErrorCode;
|
|
13
|
+
message: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function checkFiles({ file, accept, minSize, maxSize }: {
|
|
16
|
+
file: FromEventFileTypes;
|
|
17
|
+
accept: MimeTypes[] | string[] | undefined;
|
|
18
|
+
minSize: number;
|
|
19
|
+
maxSize: number;
|
|
20
|
+
}): {
|
|
21
|
+
errors: {
|
|
22
|
+
code: DropzoneErrorCode;
|
|
23
|
+
message: string;
|
|
24
|
+
}[];
|
|
25
|
+
isAccepted: boolean;
|
|
26
|
+
};
|
|
27
|
+
export declare function getFileTypeErrors(file: FromEventFileTypes, accept?: string[]): DropzoneErrorCode[];
|
|
28
|
+
export declare function getFileSizeErrors(file: FromEventFileTypes, minSize: number, maxSize: number): DropzoneErrorCode[];
|
|
29
|
+
export declare function isPropagationStopped(event: Event): boolean;
|
|
30
|
+
export declare function isEventWithFiles(event: Event | DragEvent): boolean | FileList | null | undefined;
|
|
31
|
+
export declare function isIeOrEdge(userAgent?: string): boolean;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import accepts from "./attr-accept.ts";
|
|
2
|
+
// Error codes
|
|
3
|
+
export const FILE_INVALID_TYPE = "file-invalid-type";
|
|
4
|
+
export const FILE_TOO_LARGE = "file-too-large";
|
|
5
|
+
export const FILE_TOO_SMALL = "file-too-small";
|
|
6
|
+
export const TOO_MANY_FILES = "too-many-files";
|
|
7
|
+
export const generateErrorMessage = (code, props) => {
|
|
8
|
+
const errorMessages = {
|
|
9
|
+
INVALID_FILE_TYPE: `File type must be one of ${props?.accept?.join(",")}.`,
|
|
10
|
+
FILE_TOO_LARGE: `File is larger than ${props?.maxSize} bytes.`,
|
|
11
|
+
FILE_TOO_SMALL: `File is smaller than ${props?.minSize} bytes.`,
|
|
12
|
+
TOO_MANY_FILES: `File count cannot be more than ${props?.maxFileCountPerUpload}.`,
|
|
13
|
+
CANNOT_UPLOAD_MULTIPLE_FILES: `Cannot upload multiple files.`,
|
|
14
|
+
UNKOWN: "Unkown error"
|
|
15
|
+
};
|
|
16
|
+
return { code, message: errorMessages[code] || errorMessages["UNKOWN"] };
|
|
17
|
+
};
|
|
18
|
+
export function checkFiles({ file, accept = [], minSize, maxSize }) {
|
|
19
|
+
const fileTypeErrors = getFileTypeErrors(file, accept);
|
|
20
|
+
const fileSizeErrors = getFileSizeErrors(file, minSize, maxSize);
|
|
21
|
+
const errors = [...fileSizeErrors, ...fileTypeErrors];
|
|
22
|
+
const errorWithMessages = errors.map((error) => (generateErrorMessage(error, { accept, minSize, maxSize })));
|
|
23
|
+
return { errors: errorWithMessages, isAccepted: errors?.length === 0 };
|
|
24
|
+
}
|
|
25
|
+
// Firefox versions prior to 53 return a bogus MIME type for every file drag, so dragovers with
|
|
26
|
+
// that MIME type will always be accepted
|
|
27
|
+
export function getFileTypeErrors(file, accept = []) {
|
|
28
|
+
const errors = [];
|
|
29
|
+
const isAcceptableType = file.type === "application/x-moz-file" || accepts(file, accept);
|
|
30
|
+
if (!isAcceptableType) {
|
|
31
|
+
errors.push("INVALID_FILE_TYPE");
|
|
32
|
+
}
|
|
33
|
+
return errors;
|
|
34
|
+
}
|
|
35
|
+
export function getFileSizeErrors(file, minSize, maxSize) {
|
|
36
|
+
let errors = [];
|
|
37
|
+
if (!("size" in file))
|
|
38
|
+
return errors;
|
|
39
|
+
if (isDefined(file.size)) {
|
|
40
|
+
if (isDefined(maxSize) && file.size > maxSize) {
|
|
41
|
+
errors.push("FILE_TOO_LARGE");
|
|
42
|
+
}
|
|
43
|
+
else if (isDefined(minSize) && file.size < minSize) {
|
|
44
|
+
errors.push("FILE_TOO_SMALL");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return errors;
|
|
48
|
+
}
|
|
49
|
+
function isDefined(value) {
|
|
50
|
+
return value !== undefined && value !== null;
|
|
51
|
+
}
|
|
52
|
+
// React's synthetic events has event.isPropagationStopped,
|
|
53
|
+
// but to remain compatibility with other libs (Preact) fall back
|
|
54
|
+
// to check event.cancelBubble
|
|
55
|
+
export function isPropagationStopped(event) {
|
|
56
|
+
if (typeof event.cancelBubble !== "undefined") {
|
|
57
|
+
return event.cancelBubble;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
export function isEventWithFiles(event) {
|
|
62
|
+
if ('dataTransfer' in event && event.dataTransfer) {
|
|
63
|
+
return Array.prototype.some.call(event.dataTransfer.types, (type) => type === "Files" || type === "application/x-moz-file");
|
|
64
|
+
}
|
|
65
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/types
|
|
66
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#file
|
|
67
|
+
const target = event.target;
|
|
68
|
+
return target?.files;
|
|
69
|
+
}
|
|
70
|
+
function isIe(userAgent) {
|
|
71
|
+
return (userAgent.indexOf("MSIE") !== -1 || userAgent.indexOf("Trident/") !== -1);
|
|
72
|
+
}
|
|
73
|
+
function isEdge(userAgent) {
|
|
74
|
+
return userAgent.indexOf("Edge/") !== -1;
|
|
75
|
+
}
|
|
76
|
+
export function isIeOrEdge(userAgent = window.navigator.userAgent) {
|
|
77
|
+
return isIe(userAgent) || isEdge(userAgent);
|
|
78
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export type DropzoneErrorCode = "INVALID_FILE_TYPE" | "FILE_TOO_LARGE" | "FILE_TOO_SMALL" | "TOO_MANY_FILES" | "CANNOT_UPLOAD_MULTIPLE_FILES" | "UNKOWN";
|
|
2
|
+
export type DropzoneErrorTypes = {
|
|
3
|
+
message: string;
|
|
4
|
+
code: DropzoneErrorCode;
|
|
5
|
+
} | null;
|
|
6
|
+
import type { Snippet } from "svelte";
|
|
7
|
+
import type { EventHandler } from "svelte/elements";
|
|
8
|
+
export type FromEventFileTypes = File | DataTransferItem;
|
|
9
|
+
export type RejectedFile<T> = {
|
|
10
|
+
file: T;
|
|
11
|
+
errors: DropzoneErrorTypes[];
|
|
12
|
+
};
|
|
13
|
+
export type DropzoneEvent<T> = {
|
|
14
|
+
acceptedFiles: T[];
|
|
15
|
+
rejectedFiles: RejectedFile<T>[];
|
|
16
|
+
event: DragEvent | Event;
|
|
17
|
+
};
|
|
18
|
+
export type DropzoneEventHandler<T> = (data: DropzoneEvent<T>) => void | undefined;
|
|
19
|
+
export type MimeTypes = "audio/aac" | "application/x-abiword" | "image/apng" | "application/x-freearc" | "image/avif" | "video/x-msvideo" | "application/vnd.amazon.ebook" | "application/octet-stream" | "image/bmp" | "application/x-bzip" | "application/x-bzip2" | "application/x-cdf" | "application/x-csh" | "text/css" | "text/csv" | "application/msword" | "application/vnd.openxmlformats-officedocument.wordprocessingml.document" | "application/vnd.ms-fontobject" | "application/epub+zip" | "application/gzip." | "image/gif" | "text/html" | "image/vnd.microsoft.icon" | "text/calendar" | "application/java-archive" | "image/jpeg" | "text/javascript" | "application/json" | "application/ld+json" | "audio/midi, audio/x-midi" | "text/javascript" | "audio/mpeg" | "video/mp4" | "video/mpeg" | "application/vnd.apple.installer+xml" | "application/vnd.oasis.opendocument.presentation" | "application/vnd.oasis.opendocument.spreadsheet" | "application/vnd.oasis.opendocument.text" | "audio/ogg" | "video/ogg" | "application/ogg" | "audio/ogg" | "font/otf" | "image/png" | "application/pdf" | "application/x-httpd-php" | "application/vnd.ms-powerpoint" | "application/vnd.openxmlformats-officedocument.presentationml.presentation" | "application/vnd.rar" | "application/rtf" | "application/x-sh" | "image/svg+xml" | "application/x-tar" | "image/tiff" | "video/mp2t" | "font/ttf" | "text/plain" | "application/vnd.visio" | "audio/wav" | "audio/webm" | "video/webm" | "image/webp" | "font/woff" | "font/woff2" | "application/xhtml+xml" | "application/vnd.ms-excel" | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | "application/xml" | "application/vnd.mozilla.xul+xml" | "application/zip." | "video/3gpp" | "audio/3gpp" | "video/3gpp2" | "audio/3gpp2" | "application/x-7z-compressed";
|
|
20
|
+
export interface DropzoneAreaEvents {
|
|
21
|
+
dragenter: EventHandler<DragEvent> | null;
|
|
22
|
+
dragover: EventHandler<DragEvent> | null;
|
|
23
|
+
dragleave: EventHandler<DragEvent> | null;
|
|
24
|
+
drop: EventHandler<DragEvent> | null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Custom Dropzone Event Handlers and props to make element act as dropzone inside the library.
|
|
28
|
+
* Don't override listeners in order to make library function.
|
|
29
|
+
*/
|
|
30
|
+
export interface CustomDropzoneProps {
|
|
31
|
+
'data-drozone-element-type': string;
|
|
32
|
+
id: string;
|
|
33
|
+
tabindex: number;
|
|
34
|
+
role: string;
|
|
35
|
+
onkeydown?: EventHandler<KeyboardEvent> | null;
|
|
36
|
+
onclick?: EventHandler<MouseEvent> | null;
|
|
37
|
+
}
|
|
38
|
+
export interface DropzoneProps {
|
|
39
|
+
accept?: MimeTypes[] | string[];
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
maxSize?: number;
|
|
42
|
+
minSize?: number;
|
|
43
|
+
multiple?: boolean;
|
|
44
|
+
maxFileCountPerUpload?: number;
|
|
45
|
+
preventDropOnDocument?: boolean;
|
|
46
|
+
disableDropzoneClick?: boolean;
|
|
47
|
+
disableDropzoneKeydown?: boolean;
|
|
48
|
+
disableDropzoneDrag?: boolean;
|
|
49
|
+
name?: string;
|
|
50
|
+
required?: boolean;
|
|
51
|
+
inputElement?: HTMLInputElement;
|
|
52
|
+
dropzoneElement?: HTMLElement | undefined;
|
|
53
|
+
CustomDropzone?: Snippet<[CustomDropzoneProps]> | undefined;
|
|
54
|
+
children?: Snippet;
|
|
55
|
+
onDragenter?: DropzoneEventHandler<DataTransferItem>;
|
|
56
|
+
onDragover?: DropzoneEventHandler<DataTransferItem>;
|
|
57
|
+
onDragleave?: DropzoneEventHandler<DataTransferItem>;
|
|
58
|
+
onDrop?: DropzoneEventHandler<File>;
|
|
59
|
+
onFileDialogCancel?: () => void;
|
|
60
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -31,10 +31,11 @@ export { default as Switch } from './components/form/Switch.svelte';
|
|
|
31
31
|
export { default as ChoiceInput } from './components/form/ChoiceInput.svelte';
|
|
32
32
|
export type { ChoiceInputOption } from './components/form/utils.js';
|
|
33
33
|
export { default as AntiBot } from './components/form/AntiBot.svelte';
|
|
34
|
-
export { default as Dropzone } from './components/form/Dropzone.svelte';
|
|
35
34
|
export { default as Select } from './components/form/Select.svelte';
|
|
36
35
|
export { default as Form } from './components/form/Form.svelte';
|
|
37
36
|
export { type Field, serialize, createForm, readForm, mapEntiresToOptions } from './components/form/form.js';
|
|
37
|
+
export { default as Dropzone } from './components/form/dropzone/Dropzone.svelte';
|
|
38
|
+
export * from './components/form/dropzone/types.js';
|
|
38
39
|
export { default as Markdown } from './components/content/Markdown.svelte';
|
|
39
40
|
export { type BlogPost, listAllPosts, importPost } from './components/blog/blog.js';
|
|
40
41
|
export { default as EasyTools } from './components/integrations/EasyTools.svelte';
|
package/dist/index.js
CHANGED
|
@@ -43,10 +43,11 @@ export { default as Input } from './components/form/Input.svelte';
|
|
|
43
43
|
export { default as Switch } from './components/form/Switch.svelte';
|
|
44
44
|
export { default as ChoiceInput } from './components/form/ChoiceInput.svelte';
|
|
45
45
|
export { default as AntiBot } from './components/form/AntiBot.svelte';
|
|
46
|
-
export { default as Dropzone } from './components/form/Dropzone.svelte';
|
|
47
46
|
export { default as Select } from './components/form/Select.svelte';
|
|
48
47
|
export { default as Form } from './components/form/Form.svelte';
|
|
49
48
|
export { serialize, createForm, readForm, mapEntiresToOptions } from './components/form/form.js';
|
|
49
|
+
export { default as Dropzone } from './components/form/dropzone/Dropzone.svelte';
|
|
50
|
+
export * from './components/form/dropzone/types.js';
|
|
50
51
|
/*
|
|
51
52
|
* Content
|
|
52
53
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@functionalcms/svelte-components",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.13.0",
|
|
4
4
|
"watch": {
|
|
5
5
|
"build": {
|
|
6
6
|
"patterns": [
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"eslint": "^9.26.0",
|
|
54
54
|
"eslint-config-prettier": "^10.1.2",
|
|
55
55
|
"eslint-plugin-svelte": "^3.5.1",
|
|
56
|
+
"file-selector": "^2.1.2",
|
|
56
57
|
"npm-watch": "^0.13.0",
|
|
57
58
|
"prettier": "^3.5.3",
|
|
58
59
|
"prettier-plugin-svelte": "^3.3.3",
|
|
@@ -69,6 +70,7 @@
|
|
|
69
70
|
"ioredis": "^5.6.1",
|
|
70
71
|
"marked": "^15.0.11",
|
|
71
72
|
"oauth4webapi": "^3.5.0",
|
|
73
|
+
"svelte-dropzone-runes": "^1.0.3",
|
|
72
74
|
"sveltekit-superforms": "^2.25.0",
|
|
73
75
|
"yup": "^1.6.1",
|
|
74
76
|
"zod": "^3.25.56"
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { displaySize, type FileDropZoneProps, type FileRejectedReason } from './dropzone.js';
|
|
3
|
-
import { cn } from '../../utils.js';
|
|
4
|
-
|
|
5
|
-
let {
|
|
6
|
-
id,
|
|
7
|
-
children,
|
|
8
|
-
maxFiles,
|
|
9
|
-
maxFileSize,
|
|
10
|
-
fileCount,
|
|
11
|
-
disabled = false,
|
|
12
|
-
onUpload,
|
|
13
|
-
onFileRejected,
|
|
14
|
-
accept,
|
|
15
|
-
css,
|
|
16
|
-
...rest
|
|
17
|
-
}: Partial<FileDropZoneProps> = $props();
|
|
18
|
-
|
|
19
|
-
if (maxFiles !== undefined && fileCount === undefined) {
|
|
20
|
-
console.warn(
|
|
21
|
-
'Make sure to provide FileDropZone with `fileCount` when using the `maxFiles` prompt'
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
let uploading = $state(false);
|
|
26
|
-
|
|
27
|
-
const drop = async (
|
|
28
|
-
e: DragEvent & {
|
|
29
|
-
currentTarget: EventTarget & HTMLLabelElement;
|
|
30
|
-
}
|
|
31
|
-
) => {
|
|
32
|
-
if (disabled || !canUploadFiles) return;
|
|
33
|
-
|
|
34
|
-
e.preventDefault();
|
|
35
|
-
|
|
36
|
-
const droppedFiles = Array.from(e.dataTransfer?.files ?? []);
|
|
37
|
-
|
|
38
|
-
await upload(droppedFiles);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const change = async (
|
|
42
|
-
e: Event & {
|
|
43
|
-
currentTarget: EventTarget & HTMLInputElement;
|
|
44
|
-
}
|
|
45
|
-
) => {
|
|
46
|
-
if (disabled) return;
|
|
47
|
-
|
|
48
|
-
const selectedFiles = e.currentTarget.files;
|
|
49
|
-
|
|
50
|
-
if (!selectedFiles) return;
|
|
51
|
-
|
|
52
|
-
await upload(Array.from(selectedFiles));
|
|
53
|
-
|
|
54
|
-
// this if a file fails and we upload the same file again we still get feedback
|
|
55
|
-
(e.target as HTMLInputElement).value = '';
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const shouldAcceptFile = (file: File, fileNumber: number): FileRejectedReason | undefined => {
|
|
59
|
-
if (maxFileSize !== undefined && file.size > maxFileSize) return 'Maximum file size exceeded';
|
|
60
|
-
|
|
61
|
-
if (maxFiles !== undefined && fileNumber > maxFiles) return 'Maximum files uploaded';
|
|
62
|
-
|
|
63
|
-
if (!accept) return undefined;
|
|
64
|
-
|
|
65
|
-
const acceptedTypes = accept.split(',').map((a) => a.trim().toLowerCase());
|
|
66
|
-
const fileType = file.type.toLowerCase();
|
|
67
|
-
const fileName = file.name.toLowerCase();
|
|
68
|
-
|
|
69
|
-
const isAcceptable = acceptedTypes.some((pattern) => {
|
|
70
|
-
// check extension like .mp4
|
|
71
|
-
if (fileType.startsWith('.')) {
|
|
72
|
-
return fileName.endsWith(pattern);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// if pattern has wild card like video/*
|
|
76
|
-
if (pattern.endsWith('/*')) {
|
|
77
|
-
const baseType = pattern.slice(0, pattern.indexOf('/*'));
|
|
78
|
-
return fileType.startsWith(baseType + '/');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// otherwise it must be a specific type like video/mp4
|
|
82
|
-
return fileType === pattern;
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
if (!isAcceptable) return 'File type not allowed';
|
|
86
|
-
|
|
87
|
-
return undefined;
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const upload = async (uploadFiles: File[]) => {
|
|
91
|
-
uploading = true;
|
|
92
|
-
|
|
93
|
-
const validFiles: File[] = [];
|
|
94
|
-
|
|
95
|
-
for (let i = 0; i < uploadFiles.length; i++) {
|
|
96
|
-
const file = uploadFiles[i];
|
|
97
|
-
|
|
98
|
-
const rejectedReason = shouldAcceptFile(file, (fileCount ?? 0) + i + 1);
|
|
99
|
-
|
|
100
|
-
if (rejectedReason) {
|
|
101
|
-
onFileRejected?.({ file, reason: rejectedReason });
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
validFiles.push(file);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
await onUpload(validFiles);
|
|
109
|
-
|
|
110
|
-
uploading = false;
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const canUploadFiles = $derived(
|
|
114
|
-
!disabled &&
|
|
115
|
-
!uploading &&
|
|
116
|
-
!(maxFiles !== undefined && fileCount !== undefined && fileCount >= maxFiles)
|
|
117
|
-
);
|
|
118
|
-
</script>
|
|
119
|
-
|
|
120
|
-
<label
|
|
121
|
-
ondragover={(e) => e.preventDefault()}
|
|
122
|
-
ondrop={drop}
|
|
123
|
-
for={id}
|
|
124
|
-
aria-disabled={!canUploadFiles}
|
|
125
|
-
class={cn(
|
|
126
|
-
'flex h-48 w-full place-items-center justify-center rounded-lg border-2 border-dashed border-border p-6 transition-all hover:cursor-pointer hover:bg-accent/25 aria-disabled:opacity-50 aria-disabled:hover:cursor-not-allowed',
|
|
127
|
-
css ?? ''
|
|
128
|
-
)}
|
|
129
|
-
>
|
|
130
|
-
{#if children}
|
|
131
|
-
{@render children()}
|
|
132
|
-
{:else}
|
|
133
|
-
<div class="flex flex-col place-items-center justify-center gap-2">
|
|
134
|
-
<div
|
|
135
|
-
class="flex size-14 place-items-center justify-center rounded-full border border-dashed border-border text-muted-foreground"
|
|
136
|
-
>
|
|
137
|
-
<svg
|
|
138
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
139
|
-
width="24"
|
|
140
|
-
height="24"
|
|
141
|
-
viewBox="0 0 24 24"
|
|
142
|
-
fill="none"
|
|
143
|
-
stroke="currentColor"
|
|
144
|
-
stroke-width="2"
|
|
145
|
-
stroke-linecap="round"
|
|
146
|
-
stroke-linejoin="round"
|
|
147
|
-
class="lucide lucide-upload-icon lucide-upload"
|
|
148
|
-
><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline
|
|
149
|
-
points="17 8 12 3 7 8"
|
|
150
|
-
/><line x1="12" x2="12" y1="3" y2="15" /></svg
|
|
151
|
-
>
|
|
152
|
-
</div>
|
|
153
|
-
<div class="flex flex-col gap-0.5 text-center">
|
|
154
|
-
<span class="font-medium text-muted-foreground">
|
|
155
|
-
Drag 'n' drop files here, or click to select files
|
|
156
|
-
</span>
|
|
157
|
-
{#if maxFiles || maxFileSize}
|
|
158
|
-
<span class="text-sm text-muted-foreground/75">
|
|
159
|
-
{#if maxFiles}
|
|
160
|
-
<span>You can upload {maxFiles} files</span>
|
|
161
|
-
{/if}
|
|
162
|
-
{#if maxFiles && maxFileSize}
|
|
163
|
-
<span>(up to {displaySize(maxFileSize)} each)</span>
|
|
164
|
-
{/if}
|
|
165
|
-
{#if maxFileSize && !maxFiles}
|
|
166
|
-
<span>Maximum size {displaySize(maxFileSize)}</span>
|
|
167
|
-
{/if}
|
|
168
|
-
</span>
|
|
169
|
-
{/if}
|
|
170
|
-
</div>
|
|
171
|
-
</div>
|
|
172
|
-
{/if}
|
|
173
|
-
<input
|
|
174
|
-
{...rest}
|
|
175
|
-
disabled={!canUploadFiles}
|
|
176
|
-
{id}
|
|
177
|
-
name={id}
|
|
178
|
-
{accept}
|
|
179
|
-
multiple={maxFiles === undefined || maxFiles - (fileCount ?? 0) > 1}
|
|
180
|
-
type="file"
|
|
181
|
-
onchange={change}
|
|
182
|
-
class="hidden"
|
|
183
|
-
/>
|
|
184
|
-
</label>
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
export declare const displaySize: (bytes: number) => string;
|
|
2
|
-
export declare const BYTE = 1;
|
|
3
|
-
export declare const KILOBYTE = 1024;
|
|
4
|
-
export declare const MEGABYTE: number;
|
|
5
|
-
export declare const GIGABYTE: number;
|
|
6
|
-
export declare const ACCEPT_IMAGE = "image/*";
|
|
7
|
-
export declare const ACCEPT_VIDEO = "video/*";
|
|
8
|
-
export declare const ACCEPT_AUDIO = "audio/*";
|
|
9
|
-
import type { Snippet } from 'svelte';
|
|
10
|
-
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
11
|
-
export type FileRejectedReason = 'Maximum file size exceeded' | 'File type not allowed' | 'Maximum files uploaded';
|
|
12
|
-
export interface FileDropZoneProps extends Omit<HTMLInputAttributes, 'multiple'> {
|
|
13
|
-
/** Called with the uploaded files when the user drops or clicks and selects their files.
|
|
14
|
-
*
|
|
15
|
-
* @param files
|
|
16
|
-
*/
|
|
17
|
-
onUpload: (files: File[]) => Promise<void>;
|
|
18
|
-
/** The maximum amount files allowed to be uploaded */
|
|
19
|
-
maxFiles?: number;
|
|
20
|
-
fileCount?: number;
|
|
21
|
-
/** The maximum size of a file in bytes */
|
|
22
|
-
maxFileSize?: number;
|
|
23
|
-
children?: Snippet<[]>;
|
|
24
|
-
css: string;
|
|
25
|
-
/** Called when a file does not meet the upload criteria (size, or type) */
|
|
26
|
-
onFileRejected?: (opts: {
|
|
27
|
-
reason: FileRejectedReason;
|
|
28
|
-
file: File;
|
|
29
|
-
}) => void;
|
|
30
|
-
/** Takes a comma separated list of one or more file types.
|
|
31
|
-
*
|
|
32
|
-
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept)
|
|
33
|
-
*
|
|
34
|
-
* ### Usage
|
|
35
|
-
* ```svelte
|
|
36
|
-
* <FileDropZone
|
|
37
|
-
* accept=".doc,.docx,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
38
|
-
* />
|
|
39
|
-
* ```
|
|
40
|
-
*
|
|
41
|
-
* ### Common Values
|
|
42
|
-
* ```svelte
|
|
43
|
-
* <FileDropZone accept="audio/*"/>
|
|
44
|
-
* <FileDropZone accept="image/*"/>
|
|
45
|
-
* <FileDropZone accept="video/*"/>
|
|
46
|
-
* ```
|
|
47
|
-
*/
|
|
48
|
-
accept?: string;
|
|
49
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export const displaySize = (bytes) => {
|
|
2
|
-
if (bytes < KILOBYTE)
|
|
3
|
-
return `${bytes.toFixed(0)} B`;
|
|
4
|
-
if (bytes < MEGABYTE)
|
|
5
|
-
return `${(bytes / KILOBYTE).toFixed(0)} KB`;
|
|
6
|
-
if (bytes < GIGABYTE)
|
|
7
|
-
return `${(bytes / MEGABYTE).toFixed(0)} MB`;
|
|
8
|
-
return `${(bytes / GIGABYTE).toFixed(0)} GB`;
|
|
9
|
-
};
|
|
10
|
-
// Utilities for working with file sizes
|
|
11
|
-
export const BYTE = 1;
|
|
12
|
-
export const KILOBYTE = 1024;
|
|
13
|
-
export const MEGABYTE = 1024 * KILOBYTE;
|
|
14
|
-
export const GIGABYTE = 1024 * MEGABYTE;
|
|
15
|
-
// utilities for limiting accepted files
|
|
16
|
-
export const ACCEPT_IMAGE = 'image/*';
|
|
17
|
-
export const ACCEPT_VIDEO = 'video/*';
|
|
18
|
-
export const ACCEPT_AUDIO = 'audio/*';
|