@llui/components 0.0.1
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/LICENSE +21 -0
- package/README.md +143 -0
- package/dist/components/accordion.d.ts +115 -0
- package/dist/components/accordion.d.ts.map +1 -0
- package/dist/components/accordion.js +138 -0
- package/dist/components/alert-dialog.d.ts +45 -0
- package/dist/components/alert-dialog.d.ts.map +1 -0
- package/dist/components/alert-dialog.js +12 -0
- package/dist/components/angle-slider.d.ts +121 -0
- package/dist/components/angle-slider.d.ts.map +1 -0
- package/dist/components/angle-slider.js +145 -0
- package/dist/components/async-list.d.ts +104 -0
- package/dist/components/async-list.d.ts.map +1 -0
- package/dist/components/async-list.js +117 -0
- package/dist/components/avatar.d.ts +58 -0
- package/dist/components/avatar.d.ts.map +1 -0
- package/dist/components/avatar.js +43 -0
- package/dist/components/carousel.d.ts +128 -0
- package/dist/components/carousel.d.ts.map +1 -0
- package/dist/components/carousel.js +131 -0
- package/dist/components/cascade-select.d.ts +95 -0
- package/dist/components/cascade-select.d.ts.map +1 -0
- package/dist/components/cascade-select.js +100 -0
- package/dist/components/checkbox.d.ts +74 -0
- package/dist/components/checkbox.d.ts.map +1 -0
- package/dist/components/checkbox.js +73 -0
- package/dist/components/clipboard.d.ts +72 -0
- package/dist/components/clipboard.d.ts.map +1 -0
- package/dist/components/clipboard.js +73 -0
- package/dist/components/collapsible.d.ts +64 -0
- package/dist/components/collapsible.d.ts.map +1 -0
- package/dist/components/collapsible.js +51 -0
- package/dist/components/color-picker.d.ts +125 -0
- package/dist/components/color-picker.d.ts.map +1 -0
- package/dist/components/color-picker.js +169 -0
- package/dist/components/combobox.d.ts +163 -0
- package/dist/components/combobox.d.ts.map +1 -0
- package/dist/components/combobox.js +345 -0
- package/dist/components/context-menu.d.ts +105 -0
- package/dist/components/context-menu.d.ts.map +1 -0
- package/dist/components/context-menu.js +177 -0
- package/dist/components/date-input.d.ts +117 -0
- package/dist/components/date-input.d.ts.map +1 -0
- package/dist/components/date-input.js +149 -0
- package/dist/components/date-picker.d.ts +142 -0
- package/dist/components/date-picker.d.ts.map +1 -0
- package/dist/components/date-picker.js +294 -0
- package/dist/components/dialog.d.ts +152 -0
- package/dist/components/dialog.d.ts.map +1 -0
- package/dist/components/dialog.js +140 -0
- package/dist/components/drawer.d.ts +106 -0
- package/dist/components/drawer.d.ts.map +1 -0
- package/dist/components/drawer.js +136 -0
- package/dist/components/editable.d.ts +92 -0
- package/dist/components/editable.d.ts.map +1 -0
- package/dist/components/editable.js +112 -0
- package/dist/components/file-upload.d.ts +251 -0
- package/dist/components/file-upload.d.ts.map +1 -0
- package/dist/components/file-upload.js +324 -0
- package/dist/components/floating-panel.d.ts +171 -0
- package/dist/components/floating-panel.d.ts.map +1 -0
- package/dist/components/floating-panel.js +198 -0
- package/dist/components/hover-card.d.ts +85 -0
- package/dist/components/hover-card.d.ts.map +1 -0
- package/dist/components/hover-card.js +128 -0
- package/dist/components/image-cropper.d.ts +129 -0
- package/dist/components/image-cropper.d.ts.map +1 -0
- package/dist/components/image-cropper.js +208 -0
- package/dist/components/index.d.ts +109 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +54 -0
- package/dist/components/listbox.d.ts +98 -0
- package/dist/components/listbox.d.ts.map +1 -0
- package/dist/components/listbox.js +174 -0
- package/dist/components/marquee.d.ts +84 -0
- package/dist/components/marquee.d.ts.map +1 -0
- package/dist/components/marquee.js +73 -0
- package/dist/components/menu.d.ts +131 -0
- package/dist/components/menu.d.ts.map +1 -0
- package/dist/components/menu.js +262 -0
- package/dist/components/navigation-menu.d.ts +111 -0
- package/dist/components/navigation-menu.d.ts.map +1 -0
- package/dist/components/navigation-menu.js +102 -0
- package/dist/components/number-input.d.ts +106 -0
- package/dist/components/number-input.d.ts.map +1 -0
- package/dist/components/number-input.js +178 -0
- package/dist/components/pagination.d.ts +113 -0
- package/dist/components/pagination.d.ts.map +1 -0
- package/dist/components/pagination.js +135 -0
- package/dist/components/password-input.d.ts +64 -0
- package/dist/components/password-input.d.ts.map +1 -0
- package/dist/components/password-input.js +52 -0
- package/dist/components/pin-input.d.ts +89 -0
- package/dist/components/pin-input.d.ts.map +1 -0
- package/dist/components/pin-input.js +139 -0
- package/dist/components/popover.d.ts +116 -0
- package/dist/components/popover.d.ts.map +1 -0
- package/dist/components/popover.js +146 -0
- package/dist/components/presence.d.ts +71 -0
- package/dist/components/presence.d.ts.map +1 -0
- package/dist/components/presence.js +57 -0
- package/dist/components/progress.d.ts +74 -0
- package/dist/components/progress.d.ts.map +1 -0
- package/dist/components/progress.js +80 -0
- package/dist/components/qr-code.d.ts +114 -0
- package/dist/components/qr-code.d.ts.map +1 -0
- package/dist/components/qr-code.js +108 -0
- package/dist/components/radio-group.d.ts +89 -0
- package/dist/components/radio-group.d.ts.map +1 -0
- package/dist/components/radio-group.js +161 -0
- package/dist/components/rating-group.d.ts +88 -0
- package/dist/components/rating-group.d.ts.map +1 -0
- package/dist/components/rating-group.js +122 -0
- package/dist/components/scroll-area.d.ts +124 -0
- package/dist/components/scroll-area.d.ts.map +1 -0
- package/dist/components/scroll-area.js +152 -0
- package/dist/components/select.d.ts +161 -0
- package/dist/components/select.d.ts.map +1 -0
- package/dist/components/select.js +333 -0
- package/dist/components/signature-pad.d.ts +138 -0
- package/dist/components/signature-pad.d.ts.map +1 -0
- package/dist/components/signature-pad.js +142 -0
- package/dist/components/slider.d.ts +117 -0
- package/dist/components/slider.d.ts.map +1 -0
- package/dist/components/slider.js +210 -0
- package/dist/components/splitter.d.ts +87 -0
- package/dist/components/splitter.d.ts.map +1 -0
- package/dist/components/splitter.js +119 -0
- package/dist/components/steps.d.ts +104 -0
- package/dist/components/steps.d.ts.map +1 -0
- package/dist/components/steps.js +133 -0
- package/dist/components/switch.d.ts +66 -0
- package/dist/components/switch.d.ts.map +1 -0
- package/dist/components/switch.js +59 -0
- package/dist/components/tabs.d.ts +146 -0
- package/dist/components/tabs.d.ts.map +1 -0
- package/dist/components/tabs.js +244 -0
- package/dist/components/tags-input.d.ts +118 -0
- package/dist/components/tags-input.d.ts.map +1 -0
- package/dist/components/tags-input.js +168 -0
- package/dist/components/time-picker.d.ts +121 -0
- package/dist/components/time-picker.d.ts.map +1 -0
- package/dist/components/time-picker.js +147 -0
- package/dist/components/timer.d.ts +131 -0
- package/dist/components/timer.d.ts.map +1 -0
- package/dist/components/timer.js +117 -0
- package/dist/components/toast.d.ts +119 -0
- package/dist/components/toast.d.ts.map +1 -0
- package/dist/components/toast.js +102 -0
- package/dist/components/toc.d.ts +119 -0
- package/dist/components/toc.d.ts.map +1 -0
- package/dist/components/toc.js +107 -0
- package/dist/components/toggle-group.d.ts +80 -0
- package/dist/components/toggle-group.d.ts.map +1 -0
- package/dist/components/toggle-group.js +93 -0
- package/dist/components/toggle.d.ts +47 -0
- package/dist/components/toggle.d.ts.map +1 -0
- package/dist/components/toggle.js +41 -0
- package/dist/components/tooltip.d.ts +92 -0
- package/dist/components/tooltip.d.ts.map +1 -0
- package/dist/components/tooltip.js +147 -0
- package/dist/components/tour.d.ts +145 -0
- package/dist/components/tour.d.ts.map +1 -0
- package/dist/components/tour.js +133 -0
- package/dist/components/tree-view.d.ts +216 -0
- package/dist/components/tree-view.d.ts.map +1 -0
- package/dist/components/tree-view.js +293 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/patterns/confirm-dialog.d.ts +92 -0
- package/dist/patterns/confirm-dialog.d.ts.map +1 -0
- package/dist/patterns/confirm-dialog.js +92 -0
- package/dist/patterns/index.d.ts +3 -0
- package/dist/patterns/index.d.ts.map +1 -0
- package/dist/patterns/index.js +1 -0
- package/dist/utils/anatomy.d.ts +40 -0
- package/dist/utils/anatomy.d.ts.map +1 -0
- package/dist/utils/anatomy.js +41 -0
- package/dist/utils/aria-hidden.d.ts +12 -0
- package/dist/utils/aria-hidden.d.ts.map +1 -0
- package/dist/utils/aria-hidden.js +72 -0
- package/dist/utils/dismissable.d.ts +25 -0
- package/dist/utils/dismissable.d.ts.map +1 -0
- package/dist/utils/dismissable.js +65 -0
- package/dist/utils/dom.d.ts +8 -0
- package/dist/utils/dom.d.ts.map +1 -0
- package/dist/utils/dom.js +21 -0
- package/dist/utils/floating.d.ts +44 -0
- package/dist/utils/floating.d.ts.map +1 -0
- package/dist/utils/floating.js +44 -0
- package/dist/utils/focus-trap.d.ts +18 -0
- package/dist/utils/focus-trap.d.ts.map +1 -0
- package/dist/utils/focus-trap.js +85 -0
- package/dist/utils/focusables.d.ts +6 -0
- package/dist/utils/focusables.d.ts.map +1 -0
- package/dist/utils/focusables.js +65 -0
- package/dist/utils/index.d.ts +18 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +10 -0
- package/dist/utils/interact-outside.d.ts +26 -0
- package/dist/utils/interact-outside.d.ts.map +1 -0
- package/dist/utils/interact-outside.js +46 -0
- package/dist/utils/remove-scroll.d.ts +8 -0
- package/dist/utils/remove-scroll.d.ts.map +1 -0
- package/dist/utils/remove-scroll.js +37 -0
- package/dist/utils/tree-collection.d.ts +61 -0
- package/dist/utils/tree-collection.d.ts.map +1 -0
- package/dist/utils/tree-collection.js +137 -0
- package/dist/utils/typeahead.d.ts +49 -0
- package/dist/utils/typeahead.d.ts.map +1 -0
- package/dist/utils/typeahead.js +81 -0
- package/package.json +282 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import type { Send } from '@llui/dom';
|
|
2
|
+
/**
|
|
3
|
+
* File upload — input element + drag-and-drop zone. Tracks selected files,
|
|
4
|
+
* drag state, accept filters, validation errors. Multiple or single selection.
|
|
5
|
+
*
|
|
6
|
+
* `accept` can be either a raw HTML-accept string (`"image/*,.pdf"`) or a
|
|
7
|
+
* MIME-object (`{ 'image/*': ['.png', '.jpg'], 'application/pdf': [] }`).
|
|
8
|
+
* The object form is validated client-side per file; the raw string form
|
|
9
|
+
* only drives the browser's native picker filter.
|
|
10
|
+
*
|
|
11
|
+
* Files that fail validation (too large, too small, wrong type, over the
|
|
12
|
+
* count limit) flow into `rejectedFiles` with a list of `FileError` codes
|
|
13
|
+
* attached. The view can render them alongside accepted files.
|
|
14
|
+
*/
|
|
15
|
+
export type AcceptValue = string | Record<string, string[]>;
|
|
16
|
+
export type FileError = {
|
|
17
|
+
code: 'TOO_LARGE';
|
|
18
|
+
max: number;
|
|
19
|
+
} | {
|
|
20
|
+
code: 'TOO_SMALL';
|
|
21
|
+
min: number;
|
|
22
|
+
} | {
|
|
23
|
+
code: 'INVALID_TYPE';
|
|
24
|
+
} | {
|
|
25
|
+
code: 'TOO_MANY';
|
|
26
|
+
max: number;
|
|
27
|
+
} | {
|
|
28
|
+
code: 'CUSTOM';
|
|
29
|
+
message: string;
|
|
30
|
+
};
|
|
31
|
+
export interface RejectedFile {
|
|
32
|
+
file: File;
|
|
33
|
+
errors: FileError[];
|
|
34
|
+
}
|
|
35
|
+
export interface FileUploadState {
|
|
36
|
+
files: File[];
|
|
37
|
+
rejectedFiles: RejectedFile[];
|
|
38
|
+
disabled: boolean;
|
|
39
|
+
multiple: boolean;
|
|
40
|
+
accept: AcceptValue;
|
|
41
|
+
maxFiles: number;
|
|
42
|
+
maxSize: number;
|
|
43
|
+
minFileSize: number;
|
|
44
|
+
required: boolean;
|
|
45
|
+
readOnly: boolean;
|
|
46
|
+
invalid: boolean;
|
|
47
|
+
dragging: boolean;
|
|
48
|
+
}
|
|
49
|
+
export type FileUploadMsg = {
|
|
50
|
+
type: 'setFiles';
|
|
51
|
+
files: File[];
|
|
52
|
+
customRejected?: RejectedFile[];
|
|
53
|
+
} | {
|
|
54
|
+
type: 'addFiles';
|
|
55
|
+
files: File[];
|
|
56
|
+
customRejected?: RejectedFile[];
|
|
57
|
+
} | {
|
|
58
|
+
type: 'removeFile';
|
|
59
|
+
index: number;
|
|
60
|
+
} | {
|
|
61
|
+
type: 'removeRejected';
|
|
62
|
+
index: number;
|
|
63
|
+
} | {
|
|
64
|
+
type: 'clear';
|
|
65
|
+
} | {
|
|
66
|
+
type: 'clearRejected';
|
|
67
|
+
} | {
|
|
68
|
+
type: 'dragEnter';
|
|
69
|
+
} | {
|
|
70
|
+
type: 'dragLeave';
|
|
71
|
+
} | {
|
|
72
|
+
type: 'drop';
|
|
73
|
+
} | {
|
|
74
|
+
type: 'setInvalid';
|
|
75
|
+
invalid: boolean;
|
|
76
|
+
};
|
|
77
|
+
export interface FileUploadInit {
|
|
78
|
+
files?: File[];
|
|
79
|
+
disabled?: boolean;
|
|
80
|
+
multiple?: boolean;
|
|
81
|
+
accept?: AcceptValue;
|
|
82
|
+
maxFiles?: number;
|
|
83
|
+
maxSize?: number;
|
|
84
|
+
minFileSize?: number;
|
|
85
|
+
required?: boolean;
|
|
86
|
+
readOnly?: boolean;
|
|
87
|
+
invalid?: boolean;
|
|
88
|
+
}
|
|
89
|
+
export declare function init(opts?: FileUploadInit): FileUploadState;
|
|
90
|
+
/**
|
|
91
|
+
* Serialize an AcceptValue into a comma-joined string suitable for the
|
|
92
|
+
* HTML `accept` attribute. Both MIME types and extensions are emitted.
|
|
93
|
+
*/
|
|
94
|
+
export declare function acceptToString(accept: AcceptValue): string;
|
|
95
|
+
/**
|
|
96
|
+
* Check whether a file matches the accept configuration. Raw-string accept
|
|
97
|
+
* is passed through to the browser picker so we always return true here;
|
|
98
|
+
* MIME-object accept is validated by checking MIME type (with wildcards)
|
|
99
|
+
* and extension membership.
|
|
100
|
+
*/
|
|
101
|
+
export declare function fileMatchesAccept(file: File, accept: AcceptValue): boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Partition incoming files into accepted and rejected based on state's
|
|
104
|
+
* accept/size/count constraints. The current accepted-file count is used
|
|
105
|
+
* to enforce `maxFiles` — the caller is responsible for passing the
|
|
106
|
+
* post-combine accepted total when appending.
|
|
107
|
+
*/
|
|
108
|
+
export declare function validateFiles(incoming: File[], state: FileUploadState, existingAcceptedCount: number): {
|
|
109
|
+
accepted: File[];
|
|
110
|
+
rejected: RejectedFile[];
|
|
111
|
+
};
|
|
112
|
+
export declare function update(state: FileUploadState, msg: FileUploadMsg): [FileUploadState, never[]];
|
|
113
|
+
export declare function totalSize(state: FileUploadState): number;
|
|
114
|
+
/**
|
|
115
|
+
* Install a document-level dragover/drop blocker. Without this, dragging a
|
|
116
|
+
* file outside the dropzone causes the browser to navigate away from the
|
|
117
|
+
* page. Call from onMount and invoke the returned disposer on unmount.
|
|
118
|
+
*/
|
|
119
|
+
export declare function preventDocumentDrop(): () => void;
|
|
120
|
+
export interface FileUploadItemParts<_S> {
|
|
121
|
+
item: {
|
|
122
|
+
'data-scope': 'file-upload';
|
|
123
|
+
'data-part': 'item';
|
|
124
|
+
'data-index': string;
|
|
125
|
+
};
|
|
126
|
+
itemName: {
|
|
127
|
+
'data-scope': 'file-upload';
|
|
128
|
+
'data-part': 'item-name';
|
|
129
|
+
};
|
|
130
|
+
itemSizeText: {
|
|
131
|
+
'data-scope': 'file-upload';
|
|
132
|
+
'data-part': 'item-size-text';
|
|
133
|
+
};
|
|
134
|
+
itemPreview: {
|
|
135
|
+
'data-scope': 'file-upload';
|
|
136
|
+
'data-part': 'item-preview';
|
|
137
|
+
};
|
|
138
|
+
removeTrigger: {
|
|
139
|
+
type: 'button';
|
|
140
|
+
'aria-label': string;
|
|
141
|
+
'data-scope': 'file-upload';
|
|
142
|
+
'data-part': 'item-remove';
|
|
143
|
+
onClick: (e: MouseEvent) => void;
|
|
144
|
+
};
|
|
145
|
+
/** Zag-aligned alias for removeTrigger. Same wiring. */
|
|
146
|
+
itemDeleteTrigger: {
|
|
147
|
+
type: 'button';
|
|
148
|
+
'aria-label': string;
|
|
149
|
+
'data-scope': 'file-upload';
|
|
150
|
+
'data-part': 'item-delete-trigger';
|
|
151
|
+
onClick: (e: MouseEvent) => void;
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
export interface FileUploadParts<S> {
|
|
155
|
+
root: {
|
|
156
|
+
'data-scope': 'file-upload';
|
|
157
|
+
'data-part': 'root';
|
|
158
|
+
'data-disabled': (s: S) => '' | undefined;
|
|
159
|
+
'data-dragging': (s: S) => '' | undefined;
|
|
160
|
+
'data-invalid': (s: S) => '' | undefined;
|
|
161
|
+
'data-readonly': (s: S) => '' | undefined;
|
|
162
|
+
};
|
|
163
|
+
dropzone: {
|
|
164
|
+
'data-scope': 'file-upload';
|
|
165
|
+
'data-part': 'dropzone';
|
|
166
|
+
'data-dragging': (s: S) => '' | undefined;
|
|
167
|
+
onClick: (e: MouseEvent) => void;
|
|
168
|
+
onDragEnter: (e: DragEvent) => void;
|
|
169
|
+
onDragOver: (e: DragEvent) => void;
|
|
170
|
+
onDragLeave: (e: DragEvent) => void;
|
|
171
|
+
onDrop: (e: DragEvent) => void;
|
|
172
|
+
};
|
|
173
|
+
trigger: {
|
|
174
|
+
type: 'button';
|
|
175
|
+
'data-scope': 'file-upload';
|
|
176
|
+
'data-part': 'trigger';
|
|
177
|
+
disabled: (s: S) => boolean;
|
|
178
|
+
onClick: (e: MouseEvent) => void;
|
|
179
|
+
};
|
|
180
|
+
hiddenInput: {
|
|
181
|
+
type: 'file';
|
|
182
|
+
tabIndex: -1;
|
|
183
|
+
style: string;
|
|
184
|
+
disabled: (s: S) => boolean;
|
|
185
|
+
multiple: (s: S) => boolean;
|
|
186
|
+
accept: (s: S) => string;
|
|
187
|
+
required: (s: S) => boolean;
|
|
188
|
+
'aria-invalid': (s: S) => 'true' | undefined;
|
|
189
|
+
capture?: string | boolean;
|
|
190
|
+
webkitdirectory?: '' | undefined;
|
|
191
|
+
'data-scope': 'file-upload';
|
|
192
|
+
'data-part': 'hidden-input';
|
|
193
|
+
id: string;
|
|
194
|
+
onChange: (e: Event) => void;
|
|
195
|
+
};
|
|
196
|
+
label: {
|
|
197
|
+
for: string;
|
|
198
|
+
'data-scope': 'file-upload';
|
|
199
|
+
'data-part': 'label';
|
|
200
|
+
};
|
|
201
|
+
clearTrigger: {
|
|
202
|
+
type: 'button';
|
|
203
|
+
'aria-label': string;
|
|
204
|
+
'data-scope': 'file-upload';
|
|
205
|
+
'data-part': 'clear-trigger';
|
|
206
|
+
onClick: (e: MouseEvent) => void;
|
|
207
|
+
};
|
|
208
|
+
itemGroup: {
|
|
209
|
+
'data-scope': 'file-upload';
|
|
210
|
+
'data-part': 'item-group';
|
|
211
|
+
};
|
|
212
|
+
item: (index: number) => FileUploadItemParts<S>;
|
|
213
|
+
}
|
|
214
|
+
export interface ConnectOptions {
|
|
215
|
+
id: string;
|
|
216
|
+
removeLabel?: string;
|
|
217
|
+
clearLabel?: string;
|
|
218
|
+
/**
|
|
219
|
+
* Hints the browser to use the device camera/microphone for capture. Only
|
|
220
|
+
* applies to mobile. Pass `'user'` for the front camera, `'environment'`
|
|
221
|
+
* for the back, or `true` to accept either.
|
|
222
|
+
*/
|
|
223
|
+
capture?: 'user' | 'environment' | boolean;
|
|
224
|
+
/** Show a directory-picker instead of a file-picker (webkit only). */
|
|
225
|
+
directory?: boolean;
|
|
226
|
+
/**
|
|
227
|
+
* Per-file synchronous validator. Return a non-empty array of `FileError`
|
|
228
|
+
* codes to reject the file, or null/empty to accept. Runs in addition to
|
|
229
|
+
* the state-driven accept/size/count checks — its errors accumulate into
|
|
230
|
+
* `rejectedFiles` alongside the built-in errors.
|
|
231
|
+
*/
|
|
232
|
+
validate?: (file: File) => FileError[] | null;
|
|
233
|
+
/**
|
|
234
|
+
* Optional transform pipeline. Runs before validation. Can return a
|
|
235
|
+
* Promise; onChange awaits it before dispatching. Use for image resizing,
|
|
236
|
+
* format conversion, etc.
|
|
237
|
+
*/
|
|
238
|
+
transformFiles?: (files: File[]) => File[] | Promise<File[]>;
|
|
239
|
+
}
|
|
240
|
+
export declare function connect<S>(get: (s: S) => FileUploadState, send: Send<FileUploadMsg>, opts: ConnectOptions): FileUploadParts<S>;
|
|
241
|
+
export declare const fileUpload: {
|
|
242
|
+
init: typeof init;
|
|
243
|
+
update: typeof update;
|
|
244
|
+
connect: typeof connect;
|
|
245
|
+
totalSize: typeof totalSize;
|
|
246
|
+
acceptToString: typeof acceptToString;
|
|
247
|
+
fileMatchesAccept: typeof fileMatchesAccept;
|
|
248
|
+
validateFiles: typeof validateFiles;
|
|
249
|
+
preventDocumentDrop: typeof preventDocumentDrop;
|
|
250
|
+
};
|
|
251
|
+
//# sourceMappingURL=file-upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-upload.d.ts","sourceRoot":"","sources":["../../src/components/file-upload.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
export function init(opts = {}) {
|
|
2
|
+
return {
|
|
3
|
+
files: opts.files ?? [],
|
|
4
|
+
rejectedFiles: [],
|
|
5
|
+
disabled: opts.disabled ?? false,
|
|
6
|
+
multiple: opts.multiple ?? false,
|
|
7
|
+
accept: opts.accept ?? '',
|
|
8
|
+
maxFiles: opts.maxFiles ?? 0,
|
|
9
|
+
maxSize: opts.maxSize ?? 0,
|
|
10
|
+
minFileSize: opts.minFileSize ?? 0,
|
|
11
|
+
required: opts.required ?? false,
|
|
12
|
+
readOnly: opts.readOnly ?? false,
|
|
13
|
+
invalid: opts.invalid ?? false,
|
|
14
|
+
dragging: false,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Serialize an AcceptValue into a comma-joined string suitable for the
|
|
19
|
+
* HTML `accept` attribute. Both MIME types and extensions are emitted.
|
|
20
|
+
*/
|
|
21
|
+
export function acceptToString(accept) {
|
|
22
|
+
if (typeof accept === 'string')
|
|
23
|
+
return accept;
|
|
24
|
+
const parts = [];
|
|
25
|
+
for (const [mime, exts] of Object.entries(accept)) {
|
|
26
|
+
parts.push(mime);
|
|
27
|
+
for (const ext of exts)
|
|
28
|
+
parts.push(ext);
|
|
29
|
+
}
|
|
30
|
+
return parts.join(',');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check whether a file matches the accept configuration. Raw-string accept
|
|
34
|
+
* is passed through to the browser picker so we always return true here;
|
|
35
|
+
* MIME-object accept is validated by checking MIME type (with wildcards)
|
|
36
|
+
* and extension membership.
|
|
37
|
+
*/
|
|
38
|
+
export function fileMatchesAccept(file, accept) {
|
|
39
|
+
if (typeof accept === 'string' || Object.keys(accept).length === 0)
|
|
40
|
+
return true;
|
|
41
|
+
const name = file.name.toLowerCase();
|
|
42
|
+
for (const [mime, exts] of Object.entries(accept)) {
|
|
43
|
+
if (matchMime(file.type, mime))
|
|
44
|
+
return true;
|
|
45
|
+
for (const ext of exts) {
|
|
46
|
+
if (name.endsWith(ext.toLowerCase()))
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
function matchMime(fileType, pattern) {
|
|
53
|
+
if (!fileType)
|
|
54
|
+
return false;
|
|
55
|
+
if (pattern === fileType)
|
|
56
|
+
return true;
|
|
57
|
+
// Wildcard support: "image/*" matches "image/png"
|
|
58
|
+
if (pattern.endsWith('/*')) {
|
|
59
|
+
const prefix = pattern.slice(0, -1); // "image/"
|
|
60
|
+
return fileType.startsWith(prefix);
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Partition incoming files into accepted and rejected based on state's
|
|
66
|
+
* accept/size/count constraints. The current accepted-file count is used
|
|
67
|
+
* to enforce `maxFiles` — the caller is responsible for passing the
|
|
68
|
+
* post-combine accepted total when appending.
|
|
69
|
+
*/
|
|
70
|
+
export function validateFiles(incoming, state, existingAcceptedCount) {
|
|
71
|
+
const accepted = [];
|
|
72
|
+
const rejected = [];
|
|
73
|
+
let count = existingAcceptedCount;
|
|
74
|
+
for (const f of incoming) {
|
|
75
|
+
const errors = [];
|
|
76
|
+
if (state.maxSize > 0 && f.size > state.maxSize) {
|
|
77
|
+
errors.push({ code: 'TOO_LARGE', max: state.maxSize });
|
|
78
|
+
}
|
|
79
|
+
if (state.minFileSize > 0 && f.size < state.minFileSize) {
|
|
80
|
+
errors.push({ code: 'TOO_SMALL', min: state.minFileSize });
|
|
81
|
+
}
|
|
82
|
+
if (!fileMatchesAccept(f, state.accept)) {
|
|
83
|
+
errors.push({ code: 'INVALID_TYPE' });
|
|
84
|
+
}
|
|
85
|
+
if (state.maxFiles > 0 && count >= state.maxFiles) {
|
|
86
|
+
errors.push({ code: 'TOO_MANY', max: state.maxFiles });
|
|
87
|
+
}
|
|
88
|
+
if (errors.length > 0) {
|
|
89
|
+
rejected.push({ file: f, errors });
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
accepted.push(f);
|
|
93
|
+
count++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return { accepted, rejected };
|
|
97
|
+
}
|
|
98
|
+
export function update(state, msg) {
|
|
99
|
+
if (state.disabled && msg.type !== 'clear' && msg.type !== 'clearRejected') {
|
|
100
|
+
return [state, []];
|
|
101
|
+
}
|
|
102
|
+
if (state.readOnly && (msg.type === 'setFiles' || msg.type === 'addFiles')) {
|
|
103
|
+
return [state, []];
|
|
104
|
+
}
|
|
105
|
+
switch (msg.type) {
|
|
106
|
+
case 'setFiles': {
|
|
107
|
+
const { accepted, rejected } = validateFiles(msg.files, state, 0);
|
|
108
|
+
const merged = msg.customRejected ? [...rejected, ...msg.customRejected] : rejected;
|
|
109
|
+
return [{ ...state, files: accepted, rejectedFiles: merged }, []];
|
|
110
|
+
}
|
|
111
|
+
case 'addFiles': {
|
|
112
|
+
const base = state.multiple ? state.files : [];
|
|
113
|
+
const { accepted, rejected } = validateFiles(msg.files, state, base.length);
|
|
114
|
+
const combined = state.multiple ? [...base, ...accepted] : accepted;
|
|
115
|
+
const merged = msg.customRejected ? [...rejected, ...msg.customRejected] : rejected;
|
|
116
|
+
return [{ ...state, files: combined, rejectedFiles: merged }, []];
|
|
117
|
+
}
|
|
118
|
+
case 'removeFile':
|
|
119
|
+
return [{ ...state, files: state.files.filter((_, i) => i !== msg.index) }, []];
|
|
120
|
+
case 'removeRejected':
|
|
121
|
+
return [
|
|
122
|
+
{ ...state, rejectedFiles: state.rejectedFiles.filter((_, i) => i !== msg.index) },
|
|
123
|
+
[],
|
|
124
|
+
];
|
|
125
|
+
case 'clear':
|
|
126
|
+
return [{ ...state, files: [], rejectedFiles: [] }, []];
|
|
127
|
+
case 'clearRejected':
|
|
128
|
+
return [{ ...state, rejectedFiles: [] }, []];
|
|
129
|
+
case 'setInvalid':
|
|
130
|
+
return [{ ...state, invalid: msg.invalid }, []];
|
|
131
|
+
case 'dragEnter':
|
|
132
|
+
return [{ ...state, dragging: true }, []];
|
|
133
|
+
case 'dragLeave':
|
|
134
|
+
case 'drop':
|
|
135
|
+
return [{ ...state, dragging: false }, []];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
export function totalSize(state) {
|
|
139
|
+
let total = 0;
|
|
140
|
+
for (const f of state.files)
|
|
141
|
+
total += f.size;
|
|
142
|
+
return total;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Install a document-level dragover/drop blocker. Without this, dragging a
|
|
146
|
+
* file outside the dropzone causes the browser to navigate away from the
|
|
147
|
+
* page. Call from onMount and invoke the returned disposer on unmount.
|
|
148
|
+
*/
|
|
149
|
+
export function preventDocumentDrop() {
|
|
150
|
+
const prevent = (e) => {
|
|
151
|
+
// Only prevent default if the drop is NOT on an element inside a
|
|
152
|
+
// file-upload dropzone — let those handle their own drops.
|
|
153
|
+
const target = e.target;
|
|
154
|
+
if (target?.closest('[data-scope="file-upload"][data-part="dropzone"]'))
|
|
155
|
+
return;
|
|
156
|
+
e.preventDefault();
|
|
157
|
+
};
|
|
158
|
+
document.addEventListener('dragover', prevent);
|
|
159
|
+
document.addEventListener('drop', prevent);
|
|
160
|
+
return () => {
|
|
161
|
+
document.removeEventListener('dragover', prevent);
|
|
162
|
+
document.removeEventListener('drop', prevent);
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const HIDDEN_STYLE = 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;';
|
|
166
|
+
export function connect(get, send, opts) {
|
|
167
|
+
const inputId = `${opts.id}:input`;
|
|
168
|
+
const removeLabel = opts.removeLabel ?? 'Remove file';
|
|
169
|
+
const clearLabel = opts.clearLabel ?? 'Clear files';
|
|
170
|
+
const runPipeline = async (raw) => {
|
|
171
|
+
let files = raw;
|
|
172
|
+
if (opts.transformFiles)
|
|
173
|
+
files = await opts.transformFiles(files);
|
|
174
|
+
const customRejected = [];
|
|
175
|
+
if (opts.validate) {
|
|
176
|
+
const passed = [];
|
|
177
|
+
for (const f of files) {
|
|
178
|
+
const errors = opts.validate(f);
|
|
179
|
+
if (errors && errors.length > 0)
|
|
180
|
+
customRejected.push({ file: f, errors });
|
|
181
|
+
else
|
|
182
|
+
passed.push(f);
|
|
183
|
+
}
|
|
184
|
+
files = passed;
|
|
185
|
+
}
|
|
186
|
+
return { files, customRejected };
|
|
187
|
+
};
|
|
188
|
+
const dispatchAdd = (raw) => {
|
|
189
|
+
if (!opts.transformFiles && !opts.validate) {
|
|
190
|
+
send({ type: 'addFiles', files: raw });
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
// Fire-and-forget — transforms may be async.
|
|
194
|
+
void runPipeline(raw).then(({ files, customRejected }) => {
|
|
195
|
+
send({ type: 'addFiles', files, customRejected });
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
const openPicker = (e) => {
|
|
199
|
+
const target = e.target;
|
|
200
|
+
if (target.getAttribute('data-part') === 'hidden-input')
|
|
201
|
+
return;
|
|
202
|
+
const root = e.currentTarget.closest('[data-scope="file-upload"][data-part="root"]');
|
|
203
|
+
const input = root?.querySelector('[data-scope="file-upload"][data-part="hidden-input"]');
|
|
204
|
+
input?.click();
|
|
205
|
+
};
|
|
206
|
+
return {
|
|
207
|
+
root: {
|
|
208
|
+
'data-scope': 'file-upload',
|
|
209
|
+
'data-part': 'root',
|
|
210
|
+
'data-disabled': (s) => (get(s).disabled ? '' : undefined),
|
|
211
|
+
'data-dragging': (s) => (get(s).dragging ? '' : undefined),
|
|
212
|
+
'data-invalid': (s) => (get(s).invalid ? '' : undefined),
|
|
213
|
+
'data-readonly': (s) => (get(s).readOnly ? '' : undefined),
|
|
214
|
+
},
|
|
215
|
+
dropzone: {
|
|
216
|
+
'data-scope': 'file-upload',
|
|
217
|
+
'data-part': 'dropzone',
|
|
218
|
+
'data-dragging': (s) => (get(s).dragging ? '' : undefined),
|
|
219
|
+
onClick: openPicker,
|
|
220
|
+
onDragEnter: (e) => {
|
|
221
|
+
e.preventDefault();
|
|
222
|
+
send({ type: 'dragEnter' });
|
|
223
|
+
},
|
|
224
|
+
onDragOver: (e) => e.preventDefault(),
|
|
225
|
+
onDragLeave: (e) => {
|
|
226
|
+
e.preventDefault();
|
|
227
|
+
send({ type: 'dragLeave' });
|
|
228
|
+
},
|
|
229
|
+
onDrop: (e) => {
|
|
230
|
+
e.preventDefault();
|
|
231
|
+
const files = Array.from(e.dataTransfer?.files ?? []);
|
|
232
|
+
send({ type: 'drop' });
|
|
233
|
+
dispatchAdd(files);
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
trigger: {
|
|
237
|
+
type: 'button',
|
|
238
|
+
'data-scope': 'file-upload',
|
|
239
|
+
'data-part': 'trigger',
|
|
240
|
+
disabled: (s) => get(s).disabled,
|
|
241
|
+
onClick: openPicker,
|
|
242
|
+
},
|
|
243
|
+
hiddenInput: {
|
|
244
|
+
type: 'file',
|
|
245
|
+
tabIndex: -1,
|
|
246
|
+
style: HIDDEN_STYLE,
|
|
247
|
+
disabled: (s) => get(s).disabled,
|
|
248
|
+
multiple: (s) => get(s).multiple,
|
|
249
|
+
accept: (s) => acceptToString(get(s).accept),
|
|
250
|
+
required: (s) => get(s).required,
|
|
251
|
+
'aria-invalid': (s) => (get(s).invalid ? 'true' : undefined),
|
|
252
|
+
...(opts.capture !== undefined ? { capture: opts.capture } : {}),
|
|
253
|
+
...(opts.directory === true ? { webkitdirectory: '' } : {}),
|
|
254
|
+
'data-scope': 'file-upload',
|
|
255
|
+
'data-part': 'hidden-input',
|
|
256
|
+
id: inputId,
|
|
257
|
+
onChange: (e) => {
|
|
258
|
+
const input = e.target;
|
|
259
|
+
const files = input.files ? Array.from(input.files) : [];
|
|
260
|
+
dispatchAdd(files);
|
|
261
|
+
input.value = '';
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
label: {
|
|
265
|
+
for: inputId,
|
|
266
|
+
'data-scope': 'file-upload',
|
|
267
|
+
'data-part': 'label',
|
|
268
|
+
},
|
|
269
|
+
clearTrigger: {
|
|
270
|
+
type: 'button',
|
|
271
|
+
'aria-label': clearLabel,
|
|
272
|
+
'data-scope': 'file-upload',
|
|
273
|
+
'data-part': 'clear-trigger',
|
|
274
|
+
onClick: () => send({ type: 'clear' }),
|
|
275
|
+
},
|
|
276
|
+
itemGroup: {
|
|
277
|
+
'data-scope': 'file-upload',
|
|
278
|
+
'data-part': 'item-group',
|
|
279
|
+
},
|
|
280
|
+
item: (index) => ({
|
|
281
|
+
item: {
|
|
282
|
+
'data-scope': 'file-upload',
|
|
283
|
+
'data-part': 'item',
|
|
284
|
+
'data-index': String(index),
|
|
285
|
+
},
|
|
286
|
+
itemName: {
|
|
287
|
+
'data-scope': 'file-upload',
|
|
288
|
+
'data-part': 'item-name',
|
|
289
|
+
},
|
|
290
|
+
itemSizeText: {
|
|
291
|
+
'data-scope': 'file-upload',
|
|
292
|
+
'data-part': 'item-size-text',
|
|
293
|
+
},
|
|
294
|
+
itemPreview: {
|
|
295
|
+
'data-scope': 'file-upload',
|
|
296
|
+
'data-part': 'item-preview',
|
|
297
|
+
},
|
|
298
|
+
removeTrigger: {
|
|
299
|
+
type: 'button',
|
|
300
|
+
'aria-label': removeLabel,
|
|
301
|
+
'data-scope': 'file-upload',
|
|
302
|
+
'data-part': 'item-remove',
|
|
303
|
+
onClick: () => send({ type: 'removeFile', index }),
|
|
304
|
+
},
|
|
305
|
+
itemDeleteTrigger: {
|
|
306
|
+
type: 'button',
|
|
307
|
+
'aria-label': removeLabel,
|
|
308
|
+
'data-scope': 'file-upload',
|
|
309
|
+
'data-part': 'item-delete-trigger',
|
|
310
|
+
onClick: () => send({ type: 'removeFile', index }),
|
|
311
|
+
},
|
|
312
|
+
}),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
export const fileUpload = {
|
|
316
|
+
init,
|
|
317
|
+
update,
|
|
318
|
+
connect,
|
|
319
|
+
totalSize,
|
|
320
|
+
acceptToString,
|
|
321
|
+
fileMatchesAccept,
|
|
322
|
+
validateFiles,
|
|
323
|
+
preventDocumentDrop,
|
|
324
|
+
};
|