@seed-design/react-file-upload 0.0.0-alpha-20260416134952

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.
@@ -0,0 +1,569 @@
1
+ 'use client';
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { composeRefs } from '@radix-ui/react-compose-refs';
4
+ import { elementProps, dataAttr, inputProps, buttonProps, visuallyHidden, ariaAttr, mergeProps } from '@seed-design/dom-utils';
5
+ import { Primitive } from '@seed-design/react-primitive';
6
+ import { createContext, useContext, useRef, useCallback, useEffect, useState, forwardRef } from 'react';
7
+ import { useControllableState } from '@radix-ui/react-use-controllable-state';
8
+
9
+ /**
10
+ * Image extension set sourced from mime-db (MIT license).
11
+ * @see https://www.npmjs.com/package/mime
12
+ */ const IMAGE_EXTENSIONS = new Set([
13
+ "exr",
14
+ "apng",
15
+ "avci",
16
+ "avcs",
17
+ "avif",
18
+ "bmp",
19
+ "dib",
20
+ "cgm",
21
+ "drle",
22
+ "dpx",
23
+ "emf",
24
+ "fits",
25
+ "g3",
26
+ "gif",
27
+ "heic",
28
+ "heics",
29
+ "heif",
30
+ "heifs",
31
+ "hej2",
32
+ "ief",
33
+ "jaii",
34
+ "jais",
35
+ "jls",
36
+ "jp2",
37
+ "jpg2",
38
+ "jpg",
39
+ "jpeg",
40
+ "jpe",
41
+ "jph",
42
+ "jhc",
43
+ "jpm",
44
+ "jpgm",
45
+ "jpx",
46
+ "jpf",
47
+ "jxl",
48
+ "jxr",
49
+ "jxra",
50
+ "jxrs",
51
+ "jxs",
52
+ "jxsc",
53
+ "jxsi",
54
+ "jxss",
55
+ "ktx",
56
+ "ktx2",
57
+ "jfif",
58
+ "png",
59
+ "sgi",
60
+ "svg",
61
+ "svgz",
62
+ "t38",
63
+ "tif",
64
+ "tiff",
65
+ "tfx",
66
+ "webp",
67
+ "wmf"
68
+ ]);
69
+ function isImageAccept(pattern) {
70
+ if (pattern.startsWith("image/")) return true;
71
+ if (pattern.startsWith(".")) return IMAGE_EXTENSIONS.has(pattern.slice(1).toLowerCase());
72
+ return false;
73
+ }
74
+ /**
75
+ * Determines the file accept type category based on accept patterns.
76
+ * Returns "image" if all patterns are image-related, undefined otherwise.
77
+ */ function getFileAcceptType(accept) {
78
+ if (!accept) return undefined;
79
+ const list = Array.isArray(accept) ? accept : accept.split(",").map((s)=>s.trim());
80
+ return list.every(isImageAccept) ? "image" : undefined;
81
+ }
82
+
83
+ const FileUploadContext$1 = /*#__PURE__*/ createContext(null);
84
+ const FileUploadProvider = FileUploadContext$1.Provider;
85
+ function useFileUploadContext({ strict = true } = {}) {
86
+ const context = useContext(FileUploadContext$1);
87
+ if (!context && strict) {
88
+ throw new Error("useFileUploadContext must be used within a FileUpload");
89
+ }
90
+ return context;
91
+ }
92
+ const ItemContext = /*#__PURE__*/ createContext(null);
93
+ const FileUploadItemProvider = ItemContext.Provider;
94
+ function useFileUploadItemContext({ strict = true } = {}) {
95
+ const context = useContext(ItemContext);
96
+ if (!context && strict) {
97
+ throw new Error("useFileUploadItemContext must be used within a FileUploadItem");
98
+ }
99
+ return context;
100
+ }
101
+
102
+ function syncInputFiles(inputEl, files) {
103
+ const dataTransfer = new DataTransfer();
104
+ for (const file of files){
105
+ dataTransfer.items.add(file);
106
+ }
107
+ inputEl.files = dataTransfer.files;
108
+ }
109
+ function useFileUploadState(props) {
110
+ const [acceptedFileEntries, setAcceptedFileEntries] = useControllableState({
111
+ prop: props.acceptedFileEntries,
112
+ defaultProp: props.defaultAcceptedFileEntries ?? [],
113
+ onChange: (filesWithStatus)=>{
114
+ props.onAcceptedFileEntriesChange?.(filesWithStatus);
115
+ }
116
+ });
117
+ const [isDragging, setIsDragging] = useState(false);
118
+ return {
119
+ acceptedFileEntries: acceptedFileEntries ?? [],
120
+ isDragging,
121
+ setAcceptedFileEntries,
122
+ setIsDragging
123
+ };
124
+ }
125
+ function useFileUpload({ accept, disabled = false, required = false, invalid = false, readOnly = false, maxFiles = 1, maxFileSize = Number.POSITIVE_INFINITY, minFileSize = 0, name, validate, preventDocumentDrop = true, onFileReject, onFileAccept, ...props } = {}) {
126
+ const inputRef = useRef(null);
127
+ const dropzoneRef = useRef(null);
128
+ const idCounterRef = useRef(0);
129
+ const { acceptedFileEntries, isDragging, setAcceptedFileEntries, setIsDragging } = useFileUploadState(props);
130
+ const multiple = maxFiles > 1;
131
+ const maxFilesReached = acceptedFileEntries.length >= maxFiles;
132
+ const triggerDisabled = disabled || maxFilesReached;
133
+ const acceptString = Array.isArray(accept) ? accept.join(",") : accept;
134
+ const acceptType = getFileAcceptType(accept);
135
+ const isValidFileType = useCallback((file)=>{
136
+ if (!accept) return true;
137
+ const acceptList = Array.isArray(accept) ? accept : accept.split(",").map((s)=>s.trim());
138
+ return acceptList.some((acceptPattern)=>{
139
+ // .pdf
140
+ if (acceptPattern.startsWith(".")) return file.name.toLowerCase().endsWith(acceptPattern.toLowerCase());
141
+ // image/* or image/png
142
+ if (acceptPattern.includes("*")) {
143
+ const [type] = acceptPattern.split("/");
144
+ return file.type.startsWith(`${type}/`);
145
+ }
146
+ return file.type === acceptPattern;
147
+ });
148
+ }, [
149
+ accept
150
+ ]);
151
+ const validateFiles = useCallback((files)=>{
152
+ const accepted = [];
153
+ const rejected = [];
154
+ const currentCount = acceptedFileEntries.length;
155
+ let addedCount = 0;
156
+ for (const file of files){
157
+ const errors = [];
158
+ if (currentCount + addedCount >= maxFiles) {
159
+ errors.push("TOO_MANY_FILES");
160
+ }
161
+ if (!isValidFileType(file)) {
162
+ errors.push("INVALID_TYPE");
163
+ }
164
+ if (file.size > maxFileSize) {
165
+ errors.push("FILE_TOO_LARGE");
166
+ }
167
+ if (file.size < minFileSize) {
168
+ errors.push("FILE_TOO_SMALL");
169
+ }
170
+ if (validate) {
171
+ const customErrors = validate(file);
172
+ if (customErrors) {
173
+ errors.push(...customErrors);
174
+ }
175
+ }
176
+ if (errors.length > 0) {
177
+ rejected.push({
178
+ file,
179
+ errors
180
+ });
181
+ } else {
182
+ accepted.push(file);
183
+ addedCount++;
184
+ }
185
+ }
186
+ return {
187
+ accepted,
188
+ rejected
189
+ };
190
+ }, [
191
+ maxFileSize,
192
+ minFileSize,
193
+ maxFiles,
194
+ acceptedFileEntries.length,
195
+ validate,
196
+ isValidFileType
197
+ ]);
198
+ const openFilePicker = useCallback(()=>{
199
+ if (triggerDisabled) return;
200
+ if (!inputRef.current) return;
201
+ // Reset value before opening the picker so re-selecting the same file still fires a change event
202
+ // see: https://github.com/chakra-ui/zag/blob/main/packages/machines/file-upload/src/file-upload.connect.ts
203
+ inputRef.current.value = "";
204
+ inputRef.current.click();
205
+ }, [
206
+ triggerDisabled
207
+ ]);
208
+ const updateFileEntryStatus = useCallback((id, details)=>{
209
+ setAcceptedFileEntries((prev)=>(prev ?? []).map((f)=>f.id === id ? {
210
+ ...f,
211
+ ...details
212
+ } : f));
213
+ }, [
214
+ setAcceptedFileEntries
215
+ ]);
216
+ const setFileEntries = useCallback((files)=>{
217
+ if (disabled) return;
218
+ const { accepted, rejected } = validateFiles(files);
219
+ const acceptedEntries = accepted.map((file)=>({
220
+ id: `file-${Date.now()}-${++idCounterRef.current}`,
221
+ file,
222
+ status: "pending"
223
+ }));
224
+ if (acceptedEntries.length > 0) {
225
+ const finalEntries = multiple ? acceptedEntries : [
226
+ acceptedEntries[0]
227
+ ];
228
+ if (multiple) {
229
+ setAcceptedFileEntries((prev)=>[
230
+ ...prev ?? [],
231
+ ...finalEntries
232
+ ]);
233
+ } else {
234
+ setAcceptedFileEntries(finalEntries);
235
+ }
236
+ onFileAccept?.(finalEntries, {
237
+ updateFileEntryStatus
238
+ });
239
+ }
240
+ if (rejected.length > 0) {
241
+ onFileReject?.(rejected);
242
+ }
243
+ }, [
244
+ disabled,
245
+ multiple,
246
+ validateFiles,
247
+ setAcceptedFileEntries,
248
+ onFileReject,
249
+ onFileAccept,
250
+ updateFileEntryStatus
251
+ ]);
252
+ const reorderFileEntry = useCallback((fromIndex, toIndex)=>{
253
+ if (disabled) return;
254
+ setAcceptedFileEntries((prev)=>{
255
+ const files = [
256
+ ...prev ?? []
257
+ ];
258
+ if (fromIndex < 0 || fromIndex >= files.length) return prev;
259
+ if (toIndex < 0 || toIndex >= files.length) return prev;
260
+ const [removed] = files.splice(fromIndex, 1);
261
+ files.splice(toIndex, 0, removed);
262
+ return files;
263
+ });
264
+ }, [
265
+ disabled,
266
+ setAcceptedFileEntries
267
+ ]);
268
+ const createFileUrl = useCallback((file, callback)=>{
269
+ const url = URL.createObjectURL(file);
270
+ callback(url);
271
+ return ()=>URL.revokeObjectURL(url);
272
+ }, []);
273
+ const removeFileEntry = useCallback((id)=>{
274
+ setAcceptedFileEntries((prev)=>(prev ?? []).filter((f)=>f.id !== id));
275
+ }, [
276
+ setAcceptedFileEntries
277
+ ]);
278
+ const clearFileEntries = useCallback(()=>{
279
+ setAcceptedFileEntries([]);
280
+ }, [
281
+ setAcceptedFileEntries
282
+ ]);
283
+ const stateProps = elementProps({
284
+ "data-dragging-over": dataAttr(isDragging),
285
+ "data-disabled": dataAttr(triggerDisabled),
286
+ "data-invalid": dataAttr(invalid)
287
+ });
288
+ useEffect(()=>{
289
+ if (!inputRef.current) return;
290
+ syncInputFiles(inputRef.current, acceptedFileEntries.map((e)=>e.file));
291
+ }, [
292
+ acceptedFileEntries
293
+ ]);
294
+ useEffect(()=>{
295
+ if (!preventDocumentDrop) return;
296
+ if (disabled) return;
297
+ const onDragOver = (event)=>{
298
+ if (!dropzoneRef.current) return;
299
+ event.preventDefault();
300
+ };
301
+ const onDrop = (event)=>{
302
+ if (!dropzoneRef.current) return;
303
+ const target = event.target;
304
+ if (dropzoneRef.current.contains(target)) return;
305
+ event.preventDefault();
306
+ };
307
+ document.addEventListener("dragover", onDragOver);
308
+ document.addEventListener("drop", onDrop);
309
+ return ()=>{
310
+ document.removeEventListener("dragover", onDragOver);
311
+ document.removeEventListener("drop", onDrop);
312
+ };
313
+ }, [
314
+ preventDocumentDrop,
315
+ disabled
316
+ ]);
317
+ return {
318
+ inputRef,
319
+ dropzoneRef,
320
+ acceptedFileEntries,
321
+ dragging: isDragging,
322
+ disabled,
323
+ invalid,
324
+ required,
325
+ maxFiles,
326
+ currentFileEntryCount: acceptedFileEntries.length,
327
+ acceptType,
328
+ openFilePicker,
329
+ setFileEntries,
330
+ reorderFileEntry,
331
+ createFileUrl,
332
+ updateFileEntryStatus,
333
+ removeFileEntry,
334
+ clearFileEntries,
335
+ stateProps,
336
+ dropzoneProps: elementProps({
337
+ ...stateProps,
338
+ onDragOver: (event)=>{
339
+ if (triggerDisabled) return;
340
+ event.preventDefault();
341
+ event.stopPropagation();
342
+ setIsDragging(true);
343
+ },
344
+ onDragEnter: (event)=>{
345
+ if (triggerDisabled) return;
346
+ event.preventDefault();
347
+ event.stopPropagation();
348
+ setIsDragging(true);
349
+ },
350
+ onDragLeave: (event)=>{
351
+ if (triggerDisabled) return;
352
+ event.preventDefault();
353
+ event.stopPropagation();
354
+ // Only set dragging to false if we're leaving the dropzone entirely
355
+ const relatedTarget = event.relatedTarget;
356
+ if (!event.currentTarget.contains(relatedTarget)) {
357
+ setIsDragging(false);
358
+ }
359
+ },
360
+ onDrop: (event)=>{
361
+ if (disabled) return;
362
+ event.preventDefault();
363
+ event.stopPropagation();
364
+ setIsDragging(false);
365
+ const files = event.dataTransfer?.files;
366
+ if (files && files.length > 0) {
367
+ setFileEntries(Array.from(files));
368
+ }
369
+ }
370
+ }),
371
+ triggerProps: buttonProps({
372
+ ...stateProps,
373
+ type: "button",
374
+ disabled: triggerDisabled,
375
+ onClick: openFilePicker
376
+ }),
377
+ hiddenInputProps: inputProps({
378
+ type: "file",
379
+ name,
380
+ accept: acceptString,
381
+ multiple,
382
+ disabled,
383
+ "aria-required": ariaAttr(required),
384
+ tabIndex: -1,
385
+ style: visuallyHidden,
386
+ onChange: (event)=>{
387
+ const files = event.target.files;
388
+ if (files) {
389
+ setFileEntries(Array.from(files));
390
+ }
391
+ event.target.value = "";
392
+ }
393
+ })
394
+ };
395
+ }
396
+ function useFileUploadItem(fileEntry) {
397
+ const { createFileUrl, removeFileEntry, acceptType } = useFileUploadContext();
398
+ const [isOverlayRendered, setIsOverlayRendered] = useState(false);
399
+ const overlayRef = useCallback((node)=>{
400
+ setIsOverlayRendered(!!node);
401
+ }, []);
402
+ // Image blob URL management
403
+ const [imageSrc, setImageSrc] = useState();
404
+ useEffect(()=>{
405
+ if (acceptType !== "image") return;
406
+ if (!fileEntry.file) return;
407
+ return createFileUrl(fileEntry.file, setImageSrc);
408
+ }, [
409
+ fileEntry.file,
410
+ createFileUrl,
411
+ acceptType
412
+ ]);
413
+ const overlayStateProps = elementProps({
414
+ "data-has-overlay": dataAttr(isOverlayRendered)
415
+ });
416
+ return {
417
+ ...fileEntry,
418
+ refs: {
419
+ overlay: overlayRef
420
+ },
421
+ ...acceptType === "image" && imageSrc && {
422
+ imageProps: {
423
+ src: imageSrc,
424
+ alt: fileEntry.file.name
425
+ }
426
+ },
427
+ thumbnailProps: overlayStateProps,
428
+ metadataProps: overlayStateProps,
429
+ // NOTE: `disabled` of item remove button works separately from the overall `disabled` state so we don't have stateProps here
430
+ removeButtonProps: buttonProps({
431
+ type: "button",
432
+ onClick: ()=>{
433
+ removeFileEntry(fileEntry.id);
434
+ }
435
+ })
436
+ };
437
+ }
438
+
439
+ const FileUploadRoot = /*#__PURE__*/ forwardRef(({ // UseFileUploadProps
440
+ accept, acceptedFileEntries, defaultAcceptedFileEntries, disabled, invalid, maxFileSize, maxFiles, minFileSize, name, onAcceptedFileEntriesChange, onFileAccept, onFileReject, preventDocumentDrop, readOnly, required, validate, ...otherProps }, ref)=>{
441
+ const api = useFileUpload({
442
+ accept,
443
+ acceptedFileEntries,
444
+ defaultAcceptedFileEntries,
445
+ disabled,
446
+ invalid,
447
+ maxFileSize,
448
+ maxFiles,
449
+ minFileSize,
450
+ name,
451
+ onAcceptedFileEntriesChange,
452
+ onFileAccept,
453
+ onFileReject,
454
+ preventDocumentDrop,
455
+ readOnly,
456
+ required,
457
+ validate
458
+ });
459
+ return /*#__PURE__*/ jsx(FileUploadProvider, {
460
+ value: api,
461
+ children: /*#__PURE__*/ jsx(Primitive.div, {
462
+ ref: ref,
463
+ ...api.stateProps,
464
+ ...otherProps
465
+ })
466
+ });
467
+ });
468
+ FileUploadRoot.displayName = "FileUploadRoot";
469
+ const FileUploadDropzone = /*#__PURE__*/ forwardRef((props, ref)=>{
470
+ const { dropzoneRef, dropzoneProps } = useFileUploadContext();
471
+ const mergedProps = mergeProps(dropzoneProps, props);
472
+ return /*#__PURE__*/ jsx(Primitive.div, {
473
+ ref: composeRefs(dropzoneRef, ref),
474
+ ...mergedProps
475
+ });
476
+ });
477
+ FileUploadDropzone.displayName = "FileUploadDropzone";
478
+ const FileUploadTrigger = /*#__PURE__*/ forwardRef((props, ref)=>{
479
+ const { triggerProps } = useFileUploadContext();
480
+ const mergedProps = mergeProps(triggerProps, props);
481
+ return /*#__PURE__*/ jsx(Primitive.button, {
482
+ ref: ref,
483
+ ...mergedProps
484
+ });
485
+ });
486
+ FileUploadTrigger.displayName = "FileUploadTrigger";
487
+ const FileUploadHiddenInput = /*#__PURE__*/ forwardRef((props, ref)=>{
488
+ const { inputRef, hiddenInputProps } = useFileUploadContext();
489
+ const mergedProps = mergeProps(hiddenInputProps, props);
490
+ return /*#__PURE__*/ jsx(Primitive.input, {
491
+ ref: composeRefs(inputRef, ref),
492
+ ...mergedProps
493
+ });
494
+ });
495
+ FileUploadHiddenInput.displayName = "FileUploadHiddenInput";
496
+ const FileUploadItemName = /*#__PURE__*/ forwardRef(({ children, ...props }, ref)=>{
497
+ const { stateProps } = useFileUploadContext();
498
+ const { file } = useFileUploadItemContext();
499
+ const mergedProps = mergeProps(stateProps, props);
500
+ return /*#__PURE__*/ jsx(Primitive.span, {
501
+ ref: ref,
502
+ ...mergedProps,
503
+ children: children ?? file.name
504
+ });
505
+ });
506
+ FileUploadItemName.displayName = "FileUploadItemName";
507
+ const FileUploadItemSize = /*#__PURE__*/ forwardRef(({ children, formatBytes, ...props }, ref)=>{
508
+ const { stateProps } = useFileUploadContext();
509
+ const { file } = useFileUploadItemContext();
510
+ const mergedProps = mergeProps(stateProps, props);
511
+ return /*#__PURE__*/ jsx(Primitive.span, {
512
+ ref: ref,
513
+ ...mergedProps,
514
+ children: children ?? formatBytes(file.size)
515
+ });
516
+ });
517
+ FileUploadItemSize.displayName = "FileUploadItemSize";
518
+ const FileUploadItemRemoveButton = /*#__PURE__*/ forwardRef((props, ref)=>{
519
+ const { removeButtonProps } = useFileUploadItemContext();
520
+ const mergedProps = mergeProps(removeButtonProps, props);
521
+ return /*#__PURE__*/ jsx(Primitive.button, {
522
+ ref: ref,
523
+ ...mergedProps
524
+ });
525
+ });
526
+ FileUploadItemRemoveButton.displayName = "FileUploadItemRemoveButton";
527
+ const FileUploadItemImage = /*#__PURE__*/ forwardRef((props, ref)=>{
528
+ const ctx = useFileUploadItemContext();
529
+ if (!("imageProps" in ctx)) return null;
530
+ const mergedProps = mergeProps(ctx.imageProps ?? {}, props);
531
+ return /*#__PURE__*/ jsx(Primitive.img, {
532
+ ref: ref,
533
+ ...mergedProps
534
+ });
535
+ });
536
+ FileUploadItemImage.displayName = "FileUploadItemImage";
537
+ const FileUploadItemThumbnail = /*#__PURE__*/ forwardRef((props, ref)=>{
538
+ const { thumbnailProps } = useFileUploadItemContext();
539
+ const mergedProps = mergeProps(thumbnailProps, props);
540
+ return /*#__PURE__*/ jsx(Primitive.div, {
541
+ ref: ref,
542
+ ...mergedProps
543
+ });
544
+ });
545
+ FileUploadItemThumbnail.displayName = "FileUploadItemThumbnail";
546
+ const FileUploadItemMetadata = /*#__PURE__*/ forwardRef((props, ref)=>{
547
+ const { metadataProps } = useFileUploadItemContext();
548
+ const mergedProps = mergeProps(metadataProps, props);
549
+ return /*#__PURE__*/ jsx(Primitive.div, {
550
+ ref: ref,
551
+ ...mergedProps
552
+ });
553
+ });
554
+ FileUploadItemMetadata.displayName = "FileUploadItemMetadata";
555
+ const FileUploadItemBackdrop = /*#__PURE__*/ forwardRef(({ status, children, ...props }, ref)=>{
556
+ const entry = useFileUploadItemContext();
557
+ if (entry.status !== status) return null;
558
+ return /*#__PURE__*/ jsx(Primitive.div, {
559
+ ref: composeRefs(entry.refs.overlay, ref),
560
+ ...props,
561
+ children: typeof children === "function" ? children(entry) : children
562
+ });
563
+ });
564
+ FileUploadItemBackdrop.displayName = "FileUploadItemBackdrop";
565
+ const FileUploadContext = (props)=>{
566
+ return props.children(useFileUploadContext());
567
+ };
568
+
569
+ export { FileUploadContext as F, FileUploadDropzone as a, FileUploadHiddenInput as b, FileUploadItemBackdrop as c, FileUploadItemImage as d, FileUploadItemMetadata as e, FileUploadItemName as f, FileUploadItemRemoveButton as g, FileUploadItemSize as h, FileUploadItemThumbnail as i, FileUploadRoot as j, FileUploadTrigger as k, FileUploadItemProvider as l, useFileUploadItemContext as m, useFileUpload as n, useFileUploadItem as o, useFileUploadContext as u };
package/lib/index.cjs ADDED
@@ -0,0 +1,60 @@
1
+ var FileUpload12s = require('./FileUpload-12s-1EmFGBYg.cjs');
2
+
3
+ var FileUpload_namespace = {
4
+ __proto__: null,
5
+ Context: FileUpload12s.FileUploadContext,
6
+ Dropzone: FileUpload12s.FileUploadDropzone,
7
+ HiddenInput: FileUpload12s.FileUploadHiddenInput,
8
+ ItemBackdrop: FileUpload12s.FileUploadItemBackdrop,
9
+ ItemImage: FileUpload12s.FileUploadItemImage,
10
+ ItemMetadata: FileUpload12s.FileUploadItemMetadata,
11
+ ItemName: FileUpload12s.FileUploadItemName,
12
+ ItemRemoveButton: FileUpload12s.FileUploadItemRemoveButton,
13
+ ItemSize: FileUpload12s.FileUploadItemSize,
14
+ ItemThumbnail: FileUpload12s.FileUploadItemThumbnail,
15
+ Root: FileUpload12s.FileUploadRoot,
16
+ Trigger: FileUpload12s.FileUploadTrigger
17
+ };
18
+
19
+ /**
20
+ * Split a filename into basename and extension.
21
+ *
22
+ * "document.pdf" -> { basename: "document", extension: ".pdf" }
23
+ * ".gitignore" -> { basename: ".gitignore", extension: "" }
24
+ * "README" -> { basename: "README", extension: "" }
25
+ */ function splitFileName(name) {
26
+ if (!name) return {
27
+ basename: "",
28
+ extension: ""
29
+ };
30
+ const lastDot = name.lastIndexOf(".");
31
+ // No dot, or dot is first character (dotfile)
32
+ if (lastDot <= 0) return {
33
+ basename: name,
34
+ extension: ""
35
+ };
36
+ return {
37
+ basename: name.slice(0, lastDot),
38
+ extension: name.slice(lastDot)
39
+ };
40
+ }
41
+
42
+ exports.FileUploadContext = FileUpload12s.FileUploadContext;
43
+ exports.FileUploadDropzone = FileUpload12s.FileUploadDropzone;
44
+ exports.FileUploadHiddenInput = FileUpload12s.FileUploadHiddenInput;
45
+ exports.FileUploadItemBackdrop = FileUpload12s.FileUploadItemBackdrop;
46
+ exports.FileUploadItemImage = FileUpload12s.FileUploadItemImage;
47
+ exports.FileUploadItemMetadata = FileUpload12s.FileUploadItemMetadata;
48
+ exports.FileUploadItemName = FileUpload12s.FileUploadItemName;
49
+ exports.FileUploadItemProvider = FileUpload12s.FileUploadItemProvider;
50
+ exports.FileUploadItemRemoveButton = FileUpload12s.FileUploadItemRemoveButton;
51
+ exports.FileUploadItemSize = FileUpload12s.FileUploadItemSize;
52
+ exports.FileUploadItemThumbnail = FileUpload12s.FileUploadItemThumbnail;
53
+ exports.FileUploadRoot = FileUpload12s.FileUploadRoot;
54
+ exports.FileUploadTrigger = FileUpload12s.FileUploadTrigger;
55
+ exports.useFileUpload = FileUpload12s.useFileUpload;
56
+ exports.useFileUploadContext = FileUpload12s.useFileUploadContext;
57
+ exports.useFileUploadItem = FileUpload12s.useFileUploadItem;
58
+ exports.useFileUploadItemContext = FileUpload12s.useFileUploadItemContext;
59
+ exports.FileUpload = FileUpload_namespace;
60
+ exports.splitFileName = splitFileName;