@functionalcms/svelte-components 4.16.1 → 4.19.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.
Files changed (48) hide show
  1. package/dist/components/form/Button.svelte +118 -129
  2. package/dist/components/form/Button.svelte.d.ts +0 -3
  3. package/dist/components/form/ChoiceInput.svelte +37 -80
  4. package/dist/components/form/ChoiceInput.svelte.d.ts +1 -17
  5. package/dist/components/form/Dropzone.svelte +183 -0
  6. package/dist/components/form/Dropzone.svelte.d.ts +4 -0
  7. package/dist/components/form/Form.svelte +43 -95
  8. package/dist/components/form/Form.svelte.d.ts +8 -14
  9. package/dist/components/form/Input.svelte +3 -3
  10. package/dist/components/form/Input.svelte.d.ts +3 -3
  11. package/dist/components/form/Select.svelte +39 -73
  12. package/dist/components/form/Switch.svelte +17 -19
  13. package/dist/components/form/dropzone.d.ts +49 -0
  14. package/dist/components/form/dropzone.js +18 -0
  15. package/dist/components/form/form.d.ts +8 -10
  16. package/dist/components/form/form.js +1 -33
  17. package/dist/components/layouts/Tabs.svelte +3 -3
  18. package/dist/components/layouts/TwoColumnsLayout.svelte +1 -1
  19. package/dist/components/layouts/TwoColumnsLayout.svelte.d.ts +3 -2
  20. package/dist/components/menu/CollapsibleMenu.svelte +4 -1
  21. package/dist/components/presentation/Carousel.svelte +4 -2
  22. package/dist/index-server.d.ts +1 -1
  23. package/dist/index-server.js +1 -1
  24. package/dist/index.d.ts +2 -9
  25. package/dist/index.js +1 -8
  26. package/dist/index.server.d.ts +1 -1
  27. package/dist/index.server.js +1 -1
  28. package/dist/server-side/getRedirectPipeline.d.ts +6 -0
  29. package/dist/server-side/getRedirectPipeline.js +16 -0
  30. package/package.json +82 -86
  31. package/dist/components/form/dropzone/DefaultDropzone.svelte +0 -37
  32. package/dist/components/form/dropzone/DefaultDropzone.svelte.d.ts +0 -8
  33. package/dist/components/form/dropzone/Dropzone.svelte +0 -306
  34. package/dist/components/form/dropzone/Dropzone.svelte.d.ts +0 -4
  35. package/dist/components/form/dropzone/UseDropzone.d.ts +0 -3
  36. package/dist/components/form/dropzone/UseDropzone.js +0 -19
  37. package/dist/components/form/dropzone/attr-accept.d.ts +0 -12
  38. package/dist/components/form/dropzone/attr-accept.js +0 -29
  39. package/dist/components/form/dropzone/default.d.ts +0 -31
  40. package/dist/components/form/dropzone/default.js +0 -78
  41. package/dist/components/form/dropzone/types.d.ts +0 -62
  42. package/dist/components/form/dropzone/types.js +0 -1
  43. package/dist/components/form/utils.d.ts +0 -13
  44. package/dist/components/form/utils.js +0 -1
  45. package/dist/server-side/redirection.d.ts +0 -6
  46. package/dist/server-side/redirection.js +0 -16
  47. package/dist/translations/translator.d.ts +0 -2
  48. package/dist/translations/translator.js +0 -11
@@ -1,131 +1,120 @@
1
- <script lang="ts">
2
- import { cn } from '../../utils.js';
3
- import type { Snippet } from 'svelte';
4
-
5
- interface Props {
6
- children: Snippet;
7
- css: string;
8
- style: string;
9
- type?: 'submit' | 'reset' | 'button' | 'link';
10
- href: string;
11
- mode?: string;
12
- size?: string;
13
- isPrimary: boolean;
14
- isBordered?: boolean;
15
- isCapsule?: boolean;
16
- isGrouped?: boolean;
17
- isBlock?: boolean;
18
- isLink?: boolean;
19
- isBlank?: boolean;
20
- isDisabled?: boolean;
21
- role?: string;
22
- isCircle?: boolean;
23
- isRounded?: boolean;
24
- isSkinned?: boolean;
25
- ariaSelected?: boolean;
26
- ariaControls?: string;
27
- tabIndex?: number;
28
- ariaLabel: string;
29
- noStyle: boolean;
30
- dataPreload: "hover" | "tap";
31
- codePreload: "eager" | "viewport" | "hover" | "tap";
32
- click?: (event: MouseEvent) => void;
33
- keydown?: (event: KeyboardEvent) => void;
34
- }
35
-
36
- let {
37
- children,
38
- css = '',
39
- style = '',
40
- href = '',
41
- mode = undefined,
42
- size = undefined,
43
- type = 'button',
44
- isPrimary = false,
45
- isBordered = false,
46
- isCapsule = false,
47
- isGrouped = false,
48
- isBlock = false,
49
- isLink = false,
50
- isBlank = false,
51
- isDisabled = false,
52
- role = undefined,
53
- isCircle = false,
54
- isRounded = false,
55
- isSkinned = true,
56
- noStyle = false,
57
- ariaSelected = undefined,
58
- ariaControls = undefined,
59
- tabIndex = 0,
60
- click = undefined,
61
- keydown = undefined,
62
- ariaLabel = '',
63
- dataPreload = 'hover',
64
- codePreload = 'hover',
65
- ...restProps
66
- }: Partial<Props> = $props();
67
-
68
- let klasses = $derived(
69
- noStyle
70
- ? ''
71
- : cn(
72
- isSkinned ? 'btn' : 'btn-base',
73
- mode ? `btn-${mode}` : '',
74
- size ? `btn-${size}` : '',
75
- isBordered ? 'btn-bordered' : '',
76
- isCapsule ? 'btn-capsule ' : '',
77
- isGrouped ? 'btn-grouped' : '',
78
- isBlock ? 'btn-block' : '',
79
- isCircle ? 'btn-circle' : '',
80
- isRounded ? 'btn-rounded' : '',
81
- isDisabled ? 'disabled' : '',
82
- isBlank ? 'btn-blank' : '',
83
- isLink ? 'btn-link' : '',
84
- css ? `${css}` : ''
85
- )
86
- );
87
- </script>
88
-
89
- {#if type == 'link'}
90
- <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
91
- <a
92
- class={klasses}
93
- {href}
94
- {style}
95
- {role}
96
- aria-selected={ariaSelected}
97
- aria-controls={ariaControls}
98
- aria-label={ariaLabel}
99
- tabindex={tabIndex}
100
- onclick={click}
101
- onkeydown={keydown}
102
- {...restProps}
103
- data-sveltekit-preload-data={dataPreload}
104
- data-sveltekit-preload-code={codePreload}
105
- >
106
- {@render children?.()}
107
- </a>
108
- {:else}
109
- <button
110
- {type}
111
- class={klasses}
112
- {style}
113
- {role}
114
- aria-selected={ariaSelected}
115
- aria-controls={ariaControls}
116
- aria-label={ariaLabel}
117
- tabindex={tabIndex}
118
- disabled={isDisabled}
119
- onclick={click}
120
- onkeydown={keydown}
121
- data-sveltekit-preload-data={dataPreload}
122
- data-sveltekit-preload-code={codePreload}
123
- {...restProps}
124
- >
125
- {@render children?.()}
126
- </button>
127
- {/if}
128
-
1
+ <script lang="ts">
2
+ import { cn } from '../../utils.js';
3
+ import type { Snippet } from 'svelte';
4
+ import type { EventHandler } from 'svelte/elements';
5
+
6
+ interface Props {
7
+ children: Snippet;
8
+ css: string;
9
+ style: string;
10
+ type?: 'submit' | 'reset' | 'button' | 'link';
11
+ href: string;
12
+ mode?: string;
13
+ size?: string;
14
+ isPrimary: boolean;
15
+ isBordered?: boolean;
16
+ isCapsule?: boolean;
17
+ isGrouped?: boolean;
18
+ isBlock?: boolean;
19
+ isLink?: boolean;
20
+ isBlank?: boolean;
21
+ isDisabled?: boolean;
22
+ role?: string;
23
+ isCircle?: boolean;
24
+ isRounded?: boolean;
25
+ isSkinned?: boolean;
26
+ ariaSelected?: boolean;
27
+ ariaControls?: string;
28
+ tabIndex?: number;
29
+ ariaLabel: string;
30
+ click?: (event: MouseEvent) => void;
31
+ keydown?: (event: KeyboardEvent) => void;
32
+ }
33
+
34
+ let {
35
+ children,
36
+ css = '',
37
+ style = '',
38
+ href = '',
39
+ mode = undefined,
40
+ size = undefined,
41
+ type = 'button',
42
+ isPrimary = false,
43
+ isBordered = false,
44
+ isCapsule = false,
45
+ isGrouped = false,
46
+ isBlock = false,
47
+ isLink = false,
48
+ isBlank = false,
49
+ isDisabled = false,
50
+ role = undefined,
51
+ isCircle = false,
52
+ isRounded = false,
53
+ isSkinned = true,
54
+ ariaSelected = undefined,
55
+ ariaControls = undefined,
56
+ tabIndex = 0,
57
+ click = undefined,
58
+ keydown = undefined,
59
+ ariaLabel = '',
60
+ ...restProps
61
+ }: Partial<Props> = $props();
62
+
63
+ let klasses = $derived(
64
+ cn(
65
+ isSkinned ? 'btn' : 'btn-base',
66
+ mode ? `btn-${mode}` : '',
67
+ size ? `btn-${size}` : '',
68
+ isBordered ? 'btn-bordered' : '',
69
+ isCapsule ? 'btn-capsule ' : '',
70
+ isGrouped ? 'btn-grouped' : '',
71
+ isBlock ? 'btn-block' : '',
72
+ isCircle ? 'btn-circle' : '',
73
+ isRounded ? 'btn-rounded' : '',
74
+ isDisabled ? 'disabled' : '',
75
+ isBlank ? 'btn-blank' : '',
76
+ isLink ? 'btn-link' : '',
77
+ css ? `${css}` : ''
78
+ )
79
+ );
80
+ </script>
81
+
82
+ {#if type == 'link'}
83
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
84
+ <a
85
+ class={klasses}
86
+ {href}
87
+ {style}
88
+ {role}
89
+ aria-selected={ariaSelected}
90
+ aria-controls={ariaControls}
91
+ aria-label={ariaLabel}
92
+ tabindex={tabIndex}
93
+ onclick={click}
94
+ onkeydown={keydown}
95
+ {...restProps}
96
+ >
97
+ {@render children?.()}
98
+ </a>
99
+ {:else}
100
+ <button
101
+ {type}
102
+ class={klasses}
103
+ {style}
104
+ {role}
105
+ aria-selected={ariaSelected}
106
+ aria-controls={ariaControls}
107
+ aria-label={ariaLabel}
108
+ tabindex={tabIndex}
109
+ disabled={isDisabled}
110
+ onclick={click}
111
+ onkeydown={keydown}
112
+ {...restProps}
113
+ >
114
+ {@render children?.()}
115
+ </button>
116
+ {/if}
117
+
129
118
  <style>.btn-base {
130
119
  display: inline-flex;
131
120
  align-items: center;
@@ -397,4 +386,4 @@ on the side padding. As such, these have a good bit less then regular buttons. *
397
386
 
398
387
  .btn-link:hover {
399
388
  cursor: pointer;
400
- }</style>
389
+ }</style>
@@ -23,9 +23,6 @@ declare const Button: import("svelte").Component<Partial<{
23
23
  ariaControls?: string;
24
24
  tabIndex?: number;
25
25
  ariaLabel: string;
26
- noStyle: boolean;
27
- dataPreload: "hover" | "tap";
28
- codePreload: "eager" | "viewport" | "hover" | "tap";
29
26
  click?: (event: MouseEvent) => void;
30
27
  keydown?: (event: KeyboardEvent) => void;
31
28
  }>, {}, "">;
@@ -1,6 +1,5 @@
1
1
  <script lang="ts">
2
2
  import { cn } from '../../utils.js';
3
- import { Orientation } from '../Styling.js';
4
3
  import type { ChoiceInputOption, ChoiceInputSize, ChoiceInputType, HtmlParts } from './utils.js';
5
4
 
6
5
  interface Props {
@@ -16,7 +15,6 @@
16
15
  label: string;
17
16
  size: ChoiceInputSize;
18
17
  checked: string[];
19
- orientation: Orientation;
20
18
  }
21
19
 
22
20
  let {
@@ -38,8 +36,7 @@
38
36
  size = '',
39
37
  // Provides bind:checked capabilities that consumer can use
40
38
  checked = $bindable([]),
41
- orientation = Orientation.DynamicRow,
42
- ...restProps
39
+ ...restProps
43
40
  }: Partial<Props & HtmlParts> = $props();
44
41
 
45
42
  let labelClasses = $derived(
@@ -99,41 +96,37 @@
99
96
  };
100
97
  </script>
101
98
 
102
- <div class="w-100">
103
- <fieldset class={fieldsetClasses}>
104
- <legend class={legendClasses}>{label}</legend>
105
- <div class="flex {orientation}">
106
- {#each options as { value, label }, index}
107
- <label
108
- class={labelClasses}
109
- class:disabled={isDisabled || disabledOptions.includes(value) || undefined}
110
- >
111
- <input
112
- class={inputClasses}
113
- id="{id}-{name}-{index}"
114
- {type}
115
- {name}
116
- {value}
117
- disabled={isDisabled || disabledOptions.includes(value)}
118
- checked={checkedOptions.includes(value)}
119
- onchange={onChange}
120
- {...restProps}
121
- />
122
- <span class={labelSpanClasses} aria-hidden="true"></span>
123
- <span class={labelCopyClasses}>{label}</span>
124
- </label>
125
- {/each}
126
- </div>
127
- </fieldset>
128
- </div>
99
+ <fieldset class={fieldsetClasses}>
100
+ <legend class={legendClasses}>{label}</legend>
101
+ {#each options as { value, label }, index}
102
+ <label
103
+ class={labelClasses}
104
+ class:disabled={isDisabled || disabledOptions.includes(value) || undefined}
105
+ >
106
+ <input
107
+ class={inputClasses}
108
+ id="{id}-{name}-{index}"
109
+ {type}
110
+ {name}
111
+ {value}
112
+ disabled={isDisabled || disabledOptions.includes(value)}
113
+ checked={checkedOptions.includes(value)}
114
+ onchange={onChange}
115
+ {...restProps}
116
+ />
117
+ <span class={labelSpanClasses} aria-hidden="true"></span>
118
+ <span class={labelCopyClasses}>{label}</span>
119
+ </label>
120
+ {/each}
121
+ </fieldset>
129
122
 
130
123
  <style>
131
124
  /**
132
- * These radio and checkbox customizations are an amalgamation of various resources I've
133
- * found on the internets; from Heydon Pickering's radio article (and his Inclusive Components
134
- * book), to Sara Soueidan, Scott O'Hara, MDO, and Adrian Roselli's research on the matter
135
- * of inclusive hiding and custom radio/checkbox inputs.
136
- */
125
+ * These radio and checkbox customizations are an amalgamation of various resources I've
126
+ * found on the internets; from Heydon Pickering's radio article (and his Inclusive Components
127
+ * book), to Sara Soueidan, Scott O'Hara, MDO, and Adrian Roselli's research on the matter
128
+ * of inclusive hiding and custom radio/checkbox inputs.
129
+ */
137
130
  .checkbox-group,
138
131
  .radio-group {
139
132
  --width-28: calc(7 * var(--fluid-4)); /* 1.75rem/28px */
@@ -157,13 +150,13 @@
157
150
  }
158
151
 
159
152
  /* Hiding technique from https://www.sarasoueidan.com/blog/inclusively-hiding-and-styling-checkboxes-and-radio-buttons/
160
- */
153
+ */
161
154
  .checkbox,
162
155
  .radio {
163
156
  position: absolute;
164
157
  width: var(--fluid-14);
165
158
  height: var(--fluid-14);
166
- /* opacity: 0%; */
159
+ opacity: 0%;
167
160
  }
168
161
 
169
162
  .checkbox-small,
@@ -243,7 +236,7 @@
243
236
  }
244
237
 
245
238
  /* Since we build up the radio size outwardly, it's naturally larger then the checkboxes
246
- so we add a multiplyer to even those out initially */
239
+ so we add a multiplyer to even those out initially */
247
240
  .checkbox-label::before {
248
241
  border: 2px solid var(--agnostic-checkbox-border-color, var(--agnostic-gray-light));
249
242
  width: var(--fluid-16);
@@ -333,9 +326,9 @@
333
326
  }
334
327
 
335
328
  /**
336
- * Consumer styles <legend> themselves, and can opt to use the .screenreader-only from
337
- * utilities.css if they're design requires it.
338
- */
329
+ * Consumer styles <legend> themselves, and can opt to use the .screenreader-only from
330
+ * utilities.css if they're design requires it.
331
+ */
339
332
  .checkbox-group-hidden,
340
333
  .radio-group-hidden {
341
334
  border: 0;
@@ -350,8 +343,8 @@
350
343
  }
351
344
 
352
345
  /* Targets both the label container and the span label that is used
353
- to style the custom radio / checkbox. Note it does NOT target the input
354
- itself. */
346
+ to style the custom radio / checkbox. Note it does NOT target the input
347
+ itself. */
355
348
  .checkbox[disabled] ~ .checkbox-label-copy,
356
349
  .radio[disabled] ~ .radio-label-copy,
357
350
  .checkbox-label-wrap[class='disabled'],
@@ -379,40 +372,4 @@ itself. */
379
372
  outline-offset: -2px;
380
373
  }
381
374
  }
382
-
383
-
384
- .field-help,
385
- .field-help-large,
386
- .field-help-small,
387
- .field-error,
388
- .field-error-large,
389
- .field-error-small,
390
- .label-skin,
391
- .label,
392
- .input-addon-container,
393
- .input-small,
394
- .input-large,
395
- .input-skin,
396
- .input-underlined,
397
- .input-underlined-bg,
398
- .input {
399
- color: var(--font-color, var(--dark));
400
- font-family: var(--font-family-body);
401
- font-weight: var(--font-weight, 300);
402
- font-size: var(--font-size, 1rem);
403
- line-height: var(--line-height, var(--fluid-20, 1.25rem));
404
- width: 100%;
405
- max-width: 100%;
406
- }
407
- .label {
408
- display: inline-block;
409
-
410
- /* Provided --input-vertical-pad isn't overriden we'll get 20px
411
- label w/a 6px margin then a 38px input = 64 which is on the 8pt grid */
412
- margin-block-start: 0;
413
- margin-inline-start: 0;
414
- margin-inline-end: 0;
415
- margin-block-end: var(--input-label-pad, 0.375rem);
416
- vertical-align: initial;
417
- }
418
375
  </style>
@@ -1,19 +1,3 @@
1
- import { Orientation } from '../Styling.js';
2
- import type { ChoiceInputOption, ChoiceInputSize, ChoiceInputType, HtmlParts } from './utils.js';
3
- declare const ChoiceInput: import("svelte").Component<Partial<{
4
- isSkinned: boolean;
5
- isFieldset: boolean;
6
- isInline: boolean;
7
- isDisabled: boolean;
8
- isInvalid: boolean;
9
- options: ChoiceInputOption[];
10
- disabledOptions: string[];
11
- checkedOptions: string[];
12
- type: ChoiceInputType;
13
- label: string;
14
- size: ChoiceInputSize;
15
- checked: string[];
16
- orientation: Orientation;
17
- } & HtmlParts>, {}, "checked">;
1
+ declare const ChoiceInput: import("svelte").Component<any, {}, "checked">;
18
2
  type ChoiceInput = ReturnType<typeof ChoiceInput>;
19
3
  export default ChoiceInput;
@@ -0,0 +1,183 @@
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
+ class: css,
16
+ ...rest
17
+ }: 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
+ className
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
+ {accept}
178
+ multiple={maxFiles === undefined || maxFiles - (fileCount ?? 0) > 1}
179
+ type="file"
180
+ onchange={change}
181
+ class="hidden"
182
+ />
183
+ </label>
@@ -0,0 +1,4 @@
1
+ import { type FileDropZoneProps } from './dropzone.js';
2
+ declare const Dropzone: import("svelte").Component<FileDropZoneProps, {}, "">;
3
+ type Dropzone = ReturnType<typeof Dropzone>;
4
+ export default Dropzone;