@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.
Files changed (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +143 -0
  3. package/dist/components/accordion.d.ts +115 -0
  4. package/dist/components/accordion.d.ts.map +1 -0
  5. package/dist/components/accordion.js +138 -0
  6. package/dist/components/alert-dialog.d.ts +45 -0
  7. package/dist/components/alert-dialog.d.ts.map +1 -0
  8. package/dist/components/alert-dialog.js +12 -0
  9. package/dist/components/angle-slider.d.ts +121 -0
  10. package/dist/components/angle-slider.d.ts.map +1 -0
  11. package/dist/components/angle-slider.js +145 -0
  12. package/dist/components/async-list.d.ts +104 -0
  13. package/dist/components/async-list.d.ts.map +1 -0
  14. package/dist/components/async-list.js +117 -0
  15. package/dist/components/avatar.d.ts +58 -0
  16. package/dist/components/avatar.d.ts.map +1 -0
  17. package/dist/components/avatar.js +43 -0
  18. package/dist/components/carousel.d.ts +128 -0
  19. package/dist/components/carousel.d.ts.map +1 -0
  20. package/dist/components/carousel.js +131 -0
  21. package/dist/components/cascade-select.d.ts +95 -0
  22. package/dist/components/cascade-select.d.ts.map +1 -0
  23. package/dist/components/cascade-select.js +100 -0
  24. package/dist/components/checkbox.d.ts +74 -0
  25. package/dist/components/checkbox.d.ts.map +1 -0
  26. package/dist/components/checkbox.js +73 -0
  27. package/dist/components/clipboard.d.ts +72 -0
  28. package/dist/components/clipboard.d.ts.map +1 -0
  29. package/dist/components/clipboard.js +73 -0
  30. package/dist/components/collapsible.d.ts +64 -0
  31. package/dist/components/collapsible.d.ts.map +1 -0
  32. package/dist/components/collapsible.js +51 -0
  33. package/dist/components/color-picker.d.ts +125 -0
  34. package/dist/components/color-picker.d.ts.map +1 -0
  35. package/dist/components/color-picker.js +169 -0
  36. package/dist/components/combobox.d.ts +163 -0
  37. package/dist/components/combobox.d.ts.map +1 -0
  38. package/dist/components/combobox.js +345 -0
  39. package/dist/components/context-menu.d.ts +105 -0
  40. package/dist/components/context-menu.d.ts.map +1 -0
  41. package/dist/components/context-menu.js +177 -0
  42. package/dist/components/date-input.d.ts +117 -0
  43. package/dist/components/date-input.d.ts.map +1 -0
  44. package/dist/components/date-input.js +149 -0
  45. package/dist/components/date-picker.d.ts +142 -0
  46. package/dist/components/date-picker.d.ts.map +1 -0
  47. package/dist/components/date-picker.js +294 -0
  48. package/dist/components/dialog.d.ts +152 -0
  49. package/dist/components/dialog.d.ts.map +1 -0
  50. package/dist/components/dialog.js +140 -0
  51. package/dist/components/drawer.d.ts +106 -0
  52. package/dist/components/drawer.d.ts.map +1 -0
  53. package/dist/components/drawer.js +136 -0
  54. package/dist/components/editable.d.ts +92 -0
  55. package/dist/components/editable.d.ts.map +1 -0
  56. package/dist/components/editable.js +112 -0
  57. package/dist/components/file-upload.d.ts +251 -0
  58. package/dist/components/file-upload.d.ts.map +1 -0
  59. package/dist/components/file-upload.js +324 -0
  60. package/dist/components/floating-panel.d.ts +171 -0
  61. package/dist/components/floating-panel.d.ts.map +1 -0
  62. package/dist/components/floating-panel.js +198 -0
  63. package/dist/components/hover-card.d.ts +85 -0
  64. package/dist/components/hover-card.d.ts.map +1 -0
  65. package/dist/components/hover-card.js +128 -0
  66. package/dist/components/image-cropper.d.ts +129 -0
  67. package/dist/components/image-cropper.d.ts.map +1 -0
  68. package/dist/components/image-cropper.js +208 -0
  69. package/dist/components/index.d.ts +109 -0
  70. package/dist/components/index.d.ts.map +1 -0
  71. package/dist/components/index.js +54 -0
  72. package/dist/components/listbox.d.ts +98 -0
  73. package/dist/components/listbox.d.ts.map +1 -0
  74. package/dist/components/listbox.js +174 -0
  75. package/dist/components/marquee.d.ts +84 -0
  76. package/dist/components/marquee.d.ts.map +1 -0
  77. package/dist/components/marquee.js +73 -0
  78. package/dist/components/menu.d.ts +131 -0
  79. package/dist/components/menu.d.ts.map +1 -0
  80. package/dist/components/menu.js +262 -0
  81. package/dist/components/navigation-menu.d.ts +111 -0
  82. package/dist/components/navigation-menu.d.ts.map +1 -0
  83. package/dist/components/navigation-menu.js +102 -0
  84. package/dist/components/number-input.d.ts +106 -0
  85. package/dist/components/number-input.d.ts.map +1 -0
  86. package/dist/components/number-input.js +178 -0
  87. package/dist/components/pagination.d.ts +113 -0
  88. package/dist/components/pagination.d.ts.map +1 -0
  89. package/dist/components/pagination.js +135 -0
  90. package/dist/components/password-input.d.ts +64 -0
  91. package/dist/components/password-input.d.ts.map +1 -0
  92. package/dist/components/password-input.js +52 -0
  93. package/dist/components/pin-input.d.ts +89 -0
  94. package/dist/components/pin-input.d.ts.map +1 -0
  95. package/dist/components/pin-input.js +139 -0
  96. package/dist/components/popover.d.ts +116 -0
  97. package/dist/components/popover.d.ts.map +1 -0
  98. package/dist/components/popover.js +146 -0
  99. package/dist/components/presence.d.ts +71 -0
  100. package/dist/components/presence.d.ts.map +1 -0
  101. package/dist/components/presence.js +57 -0
  102. package/dist/components/progress.d.ts +74 -0
  103. package/dist/components/progress.d.ts.map +1 -0
  104. package/dist/components/progress.js +80 -0
  105. package/dist/components/qr-code.d.ts +114 -0
  106. package/dist/components/qr-code.d.ts.map +1 -0
  107. package/dist/components/qr-code.js +108 -0
  108. package/dist/components/radio-group.d.ts +89 -0
  109. package/dist/components/radio-group.d.ts.map +1 -0
  110. package/dist/components/radio-group.js +161 -0
  111. package/dist/components/rating-group.d.ts +88 -0
  112. package/dist/components/rating-group.d.ts.map +1 -0
  113. package/dist/components/rating-group.js +122 -0
  114. package/dist/components/scroll-area.d.ts +124 -0
  115. package/dist/components/scroll-area.d.ts.map +1 -0
  116. package/dist/components/scroll-area.js +152 -0
  117. package/dist/components/select.d.ts +161 -0
  118. package/dist/components/select.d.ts.map +1 -0
  119. package/dist/components/select.js +333 -0
  120. package/dist/components/signature-pad.d.ts +138 -0
  121. package/dist/components/signature-pad.d.ts.map +1 -0
  122. package/dist/components/signature-pad.js +142 -0
  123. package/dist/components/slider.d.ts +117 -0
  124. package/dist/components/slider.d.ts.map +1 -0
  125. package/dist/components/slider.js +210 -0
  126. package/dist/components/splitter.d.ts +87 -0
  127. package/dist/components/splitter.d.ts.map +1 -0
  128. package/dist/components/splitter.js +119 -0
  129. package/dist/components/steps.d.ts +104 -0
  130. package/dist/components/steps.d.ts.map +1 -0
  131. package/dist/components/steps.js +133 -0
  132. package/dist/components/switch.d.ts +66 -0
  133. package/dist/components/switch.d.ts.map +1 -0
  134. package/dist/components/switch.js +59 -0
  135. package/dist/components/tabs.d.ts +146 -0
  136. package/dist/components/tabs.d.ts.map +1 -0
  137. package/dist/components/tabs.js +244 -0
  138. package/dist/components/tags-input.d.ts +118 -0
  139. package/dist/components/tags-input.d.ts.map +1 -0
  140. package/dist/components/tags-input.js +168 -0
  141. package/dist/components/time-picker.d.ts +121 -0
  142. package/dist/components/time-picker.d.ts.map +1 -0
  143. package/dist/components/time-picker.js +147 -0
  144. package/dist/components/timer.d.ts +131 -0
  145. package/dist/components/timer.d.ts.map +1 -0
  146. package/dist/components/timer.js +117 -0
  147. package/dist/components/toast.d.ts +119 -0
  148. package/dist/components/toast.d.ts.map +1 -0
  149. package/dist/components/toast.js +102 -0
  150. package/dist/components/toc.d.ts +119 -0
  151. package/dist/components/toc.d.ts.map +1 -0
  152. package/dist/components/toc.js +107 -0
  153. package/dist/components/toggle-group.d.ts +80 -0
  154. package/dist/components/toggle-group.d.ts.map +1 -0
  155. package/dist/components/toggle-group.js +93 -0
  156. package/dist/components/toggle.d.ts +47 -0
  157. package/dist/components/toggle.d.ts.map +1 -0
  158. package/dist/components/toggle.js +41 -0
  159. package/dist/components/tooltip.d.ts +92 -0
  160. package/dist/components/tooltip.d.ts.map +1 -0
  161. package/dist/components/tooltip.js +147 -0
  162. package/dist/components/tour.d.ts +145 -0
  163. package/dist/components/tour.d.ts.map +1 -0
  164. package/dist/components/tour.js +133 -0
  165. package/dist/components/tree-view.d.ts +216 -0
  166. package/dist/components/tree-view.d.ts.map +1 -0
  167. package/dist/components/tree-view.js +293 -0
  168. package/dist/index.d.ts +3 -0
  169. package/dist/index.d.ts.map +1 -0
  170. package/dist/index.js +4 -0
  171. package/dist/patterns/confirm-dialog.d.ts +92 -0
  172. package/dist/patterns/confirm-dialog.d.ts.map +1 -0
  173. package/dist/patterns/confirm-dialog.js +92 -0
  174. package/dist/patterns/index.d.ts +3 -0
  175. package/dist/patterns/index.d.ts.map +1 -0
  176. package/dist/patterns/index.js +1 -0
  177. package/dist/utils/anatomy.d.ts +40 -0
  178. package/dist/utils/anatomy.d.ts.map +1 -0
  179. package/dist/utils/anatomy.js +41 -0
  180. package/dist/utils/aria-hidden.d.ts +12 -0
  181. package/dist/utils/aria-hidden.d.ts.map +1 -0
  182. package/dist/utils/aria-hidden.js +72 -0
  183. package/dist/utils/dismissable.d.ts +25 -0
  184. package/dist/utils/dismissable.d.ts.map +1 -0
  185. package/dist/utils/dismissable.js +65 -0
  186. package/dist/utils/dom.d.ts +8 -0
  187. package/dist/utils/dom.d.ts.map +1 -0
  188. package/dist/utils/dom.js +21 -0
  189. package/dist/utils/floating.d.ts +44 -0
  190. package/dist/utils/floating.d.ts.map +1 -0
  191. package/dist/utils/floating.js +44 -0
  192. package/dist/utils/focus-trap.d.ts +18 -0
  193. package/dist/utils/focus-trap.d.ts.map +1 -0
  194. package/dist/utils/focus-trap.js +85 -0
  195. package/dist/utils/focusables.d.ts +6 -0
  196. package/dist/utils/focusables.d.ts.map +1 -0
  197. package/dist/utils/focusables.js +65 -0
  198. package/dist/utils/index.d.ts +18 -0
  199. package/dist/utils/index.d.ts.map +1 -0
  200. package/dist/utils/index.js +10 -0
  201. package/dist/utils/interact-outside.d.ts +26 -0
  202. package/dist/utils/interact-outside.d.ts.map +1 -0
  203. package/dist/utils/interact-outside.js +46 -0
  204. package/dist/utils/remove-scroll.d.ts +8 -0
  205. package/dist/utils/remove-scroll.d.ts.map +1 -0
  206. package/dist/utils/remove-scroll.js +37 -0
  207. package/dist/utils/tree-collection.d.ts +61 -0
  208. package/dist/utils/tree-collection.d.ts.map +1 -0
  209. package/dist/utils/tree-collection.js +137 -0
  210. package/dist/utils/typeahead.d.ts +49 -0
  211. package/dist/utils/typeahead.d.ts.map +1 -0
  212. package/dist/utils/typeahead.js +81 -0
  213. 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
+ };