@seed-design/react-file-upload 0.0.0-alpha-20260511052324 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/{FileUpload-12s-BsPs5ndy.js → FileUpload-12s-BJqUtE7a.js} +24 -37
- package/lib/{FileUpload-12s-1EmFGBYg.cjs → FileUpload-12s-nKKpZreT.cjs} +23 -38
- package/lib/index.cjs +1 -5
- package/lib/index.d.ts +1548 -6090
- package/lib/index.js +3 -5
- package/package.json +4 -4
- package/src/FileUpload.namespace.ts +0 -4
- package/src/FileUpload.tsx +1 -31
- package/src/index.ts +0 -4
- package/src/useFileUpload.test.tsx +144 -0
- package/src/useFileUpload.ts +22 -23
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as FileUploadContext, a as FileUploadDropzone, b as FileUploadHiddenInput, c as FileUploadItemBackdrop, d as FileUploadItemImage, e as
|
|
2
|
-
export {
|
|
1
|
+
import { F as FileUploadContext, a as FileUploadDropzone, b as FileUploadHiddenInput, c as FileUploadItemBackdrop, d as FileUploadItemImage, e as FileUploadItemName, f as FileUploadItemRemoveButton, g as FileUploadItemSize, h as FileUploadRoot, i as FileUploadTrigger } from './FileUpload-12s-BJqUtE7a.js';
|
|
2
|
+
export { j as FileUploadItemProvider, l as useFileUpload, u as useFileUploadContext, m as useFileUploadItem, k as useFileUploadItemContext } from './FileUpload-12s-BJqUtE7a.js';
|
|
3
3
|
|
|
4
4
|
var FileUpload_namespace = {
|
|
5
5
|
__proto__: null,
|
|
@@ -8,11 +8,9 @@ var FileUpload_namespace = {
|
|
|
8
8
|
HiddenInput: FileUploadHiddenInput,
|
|
9
9
|
ItemBackdrop: FileUploadItemBackdrop,
|
|
10
10
|
ItemImage: FileUploadItemImage,
|
|
11
|
-
ItemMetadata: FileUploadItemMetadata,
|
|
12
11
|
ItemName: FileUploadItemName,
|
|
13
12
|
ItemRemoveButton: FileUploadItemRemoveButton,
|
|
14
13
|
ItemSize: FileUploadItemSize,
|
|
15
|
-
ItemThumbnail: FileUploadItemThumbnail,
|
|
16
14
|
Root: FileUploadRoot,
|
|
17
15
|
Trigger: FileUploadTrigger
|
|
18
16
|
};
|
|
@@ -40,4 +38,4 @@ var FileUpload_namespace = {
|
|
|
40
38
|
};
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
export { FileUpload_namespace as FileUpload, FileUploadContext, FileUploadDropzone, FileUploadHiddenInput, FileUploadItemBackdrop, FileUploadItemImage,
|
|
41
|
+
export { FileUpload_namespace as FileUpload, FileUploadContext, FileUploadDropzone, FileUploadHiddenInput, FileUploadItemBackdrop, FileUploadItemImage, FileUploadItemName, FileUploadItemRemoveButton, FileUploadItemSize, FileUploadRoot, FileUploadTrigger, splitFileName };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seed-design/react-file-upload",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/daangn/seed-design.git",
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@radix-ui/react-compose-refs": "1.1.2",
|
|
31
31
|
"@radix-ui/react-use-controllable-state": "1.2.2",
|
|
32
|
-
"@seed-design/dom-utils": "
|
|
33
|
-
"@seed-design/react-primitive": "
|
|
34
|
-
"@seed-design/react-supports": "0.0
|
|
32
|
+
"@seed-design/dom-utils": "^2.0.0",
|
|
33
|
+
"@seed-design/react-primitive": "^2.0.0",
|
|
34
|
+
"@seed-design/react-supports": "^1.0.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/react": "^19.1.6",
|
|
@@ -7,8 +7,6 @@ export {
|
|
|
7
7
|
FileUploadItemSize as ItemSize,
|
|
8
8
|
FileUploadItemRemoveButton as ItemRemoveButton,
|
|
9
9
|
FileUploadItemImage as ItemImage,
|
|
10
|
-
FileUploadItemThumbnail as ItemThumbnail,
|
|
11
|
-
FileUploadItemMetadata as ItemMetadata,
|
|
12
10
|
FileUploadItemBackdrop as ItemBackdrop,
|
|
13
11
|
FileUploadContext as Context,
|
|
14
12
|
type FileUploadRootProps as RootProps,
|
|
@@ -19,8 +17,6 @@ export {
|
|
|
19
17
|
type FileUploadItemSizeProps as ItemSizeProps,
|
|
20
18
|
type FileUploadItemRemoveButtonProps as ItemRemoveButtonProps,
|
|
21
19
|
type FileUploadItemImageProps as ItemImageProps,
|
|
22
|
-
type FileUploadItemThumbnailProps as ItemThumbnailProps,
|
|
23
|
-
type FileUploadItemMetadataProps as ItemMetadataProps,
|
|
24
20
|
type FileUploadItemBackdropProps as ItemBackdropProps,
|
|
25
21
|
type FileUploadContextProps as ContextProps,
|
|
26
22
|
} from "./FileUpload";
|
package/src/FileUpload.tsx
CHANGED
|
@@ -192,36 +192,6 @@ export const FileUploadItemImage = forwardRef<HTMLImageElement, FileUploadItemIm
|
|
|
192
192
|
);
|
|
193
193
|
FileUploadItemImage.displayName = "FileUploadItemImage";
|
|
194
194
|
|
|
195
|
-
export interface FileUploadItemThumbnailProps
|
|
196
|
-
extends PrimitiveProps,
|
|
197
|
-
React.HTMLAttributes<HTMLDivElement> {}
|
|
198
|
-
|
|
199
|
-
export const FileUploadItemThumbnail = forwardRef<HTMLDivElement, FileUploadItemThumbnailProps>(
|
|
200
|
-
(props, ref) => {
|
|
201
|
-
const { thumbnailProps } = useFileUploadItemContext();
|
|
202
|
-
|
|
203
|
-
const mergedProps = mergeProps(thumbnailProps, props);
|
|
204
|
-
|
|
205
|
-
return <Primitive.div ref={ref} {...mergedProps} />;
|
|
206
|
-
},
|
|
207
|
-
);
|
|
208
|
-
FileUploadItemThumbnail.displayName = "FileUploadItemThumbnail";
|
|
209
|
-
|
|
210
|
-
export interface FileUploadItemMetadataProps
|
|
211
|
-
extends PrimitiveProps,
|
|
212
|
-
React.HTMLAttributes<HTMLDivElement> {}
|
|
213
|
-
|
|
214
|
-
export const FileUploadItemMetadata = forwardRef<HTMLDivElement, FileUploadItemMetadataProps>(
|
|
215
|
-
(props, ref) => {
|
|
216
|
-
const { metadataProps } = useFileUploadItemContext();
|
|
217
|
-
|
|
218
|
-
const mergedProps = mergeProps(metadataProps, props);
|
|
219
|
-
|
|
220
|
-
return <Primitive.div ref={ref} {...mergedProps} />;
|
|
221
|
-
},
|
|
222
|
-
);
|
|
223
|
-
FileUploadItemMetadata.displayName = "FileUploadItemMetadata";
|
|
224
|
-
|
|
225
195
|
export interface FileUploadItemBackdropProps
|
|
226
196
|
extends PrimitiveProps,
|
|
227
197
|
Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
|
|
@@ -236,7 +206,7 @@ export const FileUploadItemBackdrop = forwardRef<HTMLDivElement, FileUploadItemB
|
|
|
236
206
|
if (entry.status !== status) return null;
|
|
237
207
|
|
|
238
208
|
return (
|
|
239
|
-
<Primitive.div ref={
|
|
209
|
+
<Primitive.div ref={ref} {...props}>
|
|
240
210
|
{typeof children === "function" ? children(entry) : children}
|
|
241
211
|
</Primitive.div>
|
|
242
212
|
);
|
package/src/index.ts
CHANGED
|
@@ -7,8 +7,6 @@ export {
|
|
|
7
7
|
FileUploadItemSize,
|
|
8
8
|
FileUploadItemRemoveButton,
|
|
9
9
|
FileUploadItemImage,
|
|
10
|
-
FileUploadItemThumbnail,
|
|
11
|
-
FileUploadItemMetadata,
|
|
12
10
|
FileUploadItemBackdrop,
|
|
13
11
|
FileUploadContext,
|
|
14
12
|
type FileUploadRootProps,
|
|
@@ -19,8 +17,6 @@ export {
|
|
|
19
17
|
type FileUploadItemSizeProps,
|
|
20
18
|
type FileUploadItemRemoveButtonProps,
|
|
21
19
|
type FileUploadItemImageProps,
|
|
22
|
-
type FileUploadItemThumbnailProps,
|
|
23
|
-
type FileUploadItemMetadataProps,
|
|
24
20
|
type FileUploadItemBackdropProps,
|
|
25
21
|
type FileUploadContextProps,
|
|
26
22
|
} from "./FileUpload";
|
|
@@ -87,6 +87,7 @@ describe("useFileUpload", () => {
|
|
|
87
87
|
expect(input.type).toBe("file");
|
|
88
88
|
expect(input.name).toBe("files");
|
|
89
89
|
expect(input.tabIndex).toBe(-1);
|
|
90
|
+
expect(input.getAttribute("aria-hidden")).toBe("true");
|
|
90
91
|
});
|
|
91
92
|
|
|
92
93
|
it("should render with multiple attribute when maxFiles > 1", () => {
|
|
@@ -479,6 +480,149 @@ describe("useFileUpload", () => {
|
|
|
479
480
|
});
|
|
480
481
|
});
|
|
481
482
|
|
|
483
|
+
describe("readonly state", () => {
|
|
484
|
+
const seedFiles: FileEntry[] = [
|
|
485
|
+
{ id: "seed-1", file: createMockFile("seed.txt", 100, "text/plain"), status: "success" },
|
|
486
|
+
];
|
|
487
|
+
|
|
488
|
+
it("should have data-readonly attribute on dropzone when readOnly", () => {
|
|
489
|
+
const { getByTestId } = setUp(<BasicFileUpload readOnly />);
|
|
490
|
+
const dropzone = getByTestId("dropzone");
|
|
491
|
+
|
|
492
|
+
expect(dropzone.getAttribute("data-readonly")).toBe("");
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it("should disable trigger when readOnly", () => {
|
|
496
|
+
const { getByText } = setUp(<BasicFileUpload readOnly />);
|
|
497
|
+
const trigger = getByText("Choose files") as HTMLButtonElement;
|
|
498
|
+
|
|
499
|
+
expect(trigger.disabled).toBe(true);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it("should NOT disable hidden input when readOnly so the form value is preserved", () => {
|
|
503
|
+
const { getByTestId } = setUp(
|
|
504
|
+
<BasicFileUpload readOnly defaultAcceptedFileEntries={seedFiles} />,
|
|
505
|
+
);
|
|
506
|
+
const input = getByTestId("hidden-input") as HTMLInputElement;
|
|
507
|
+
|
|
508
|
+
expect(input.disabled).toBe(false);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it("should keep input.files in sync when readOnly (form submission carries the values)", async () => {
|
|
512
|
+
const { getByTestId } = setUp(
|
|
513
|
+
<BasicFileUpload readOnly defaultAcceptedFileEntries={seedFiles} />,
|
|
514
|
+
);
|
|
515
|
+
const input = getByTestId("hidden-input") as HTMLInputElement;
|
|
516
|
+
|
|
517
|
+
await waitFor(() => {
|
|
518
|
+
expect(input.files?.length).toBe(1);
|
|
519
|
+
expect(input.files?.[0]?.name).toBe("seed.txt");
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
it("should not accept files via input change when readOnly", async () => {
|
|
524
|
+
const onAcceptedFileEntriesChange = mock(() => {});
|
|
525
|
+
const { getByTestId } = setUp(
|
|
526
|
+
<BasicFileUpload readOnly onAcceptedFileEntriesChange={onAcceptedFileEntriesChange} />,
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
const input = getByTestId("hidden-input") as HTMLInputElement;
|
|
530
|
+
fireEvent.change(input, {
|
|
531
|
+
target: { files: [createMockFile("new.txt", 100, "text/plain")] },
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
535
|
+
expect(onAcceptedFileEntriesChange).not.toHaveBeenCalled();
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it("should not accept dropped files when readOnly", async () => {
|
|
539
|
+
const onAcceptedFileEntriesChange = mock(() => {});
|
|
540
|
+
const { getByTestId } = setUp(
|
|
541
|
+
<BasicFileUpload readOnly onAcceptedFileEntriesChange={onAcceptedFileEntriesChange} />,
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
const dropzone = getByTestId("dropzone");
|
|
545
|
+
fireEvent.drop(dropzone, {
|
|
546
|
+
dataTransfer: { files: [createMockFile("dropped.txt", 100, "text/plain")] },
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
550
|
+
expect(onAcceptedFileEntriesChange).not.toHaveBeenCalled();
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it("should disable remove button on items when readOnly", () => {
|
|
554
|
+
const { getByTestId } = setUp(
|
|
555
|
+
<BasicFileUpload readOnly defaultAcceptedFileEntries={seedFiles} />,
|
|
556
|
+
);
|
|
557
|
+
const deleteButton = getByTestId("delete-0") as HTMLButtonElement;
|
|
558
|
+
|
|
559
|
+
expect(deleteButton.disabled).toBe(true);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it("should not remove items when readOnly", async () => {
|
|
563
|
+
const onAcceptedFileEntriesChange = mock(() => {});
|
|
564
|
+
const { getByTestId, user } = setUp(
|
|
565
|
+
<BasicFileUpload
|
|
566
|
+
readOnly
|
|
567
|
+
defaultAcceptedFileEntries={seedFiles}
|
|
568
|
+
onAcceptedFileEntriesChange={onAcceptedFileEntriesChange}
|
|
569
|
+
/>,
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
const deleteButton = getByTestId("delete-0");
|
|
573
|
+
await user.click(deleteButton);
|
|
574
|
+
|
|
575
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
576
|
+
expect(onAcceptedFileEntriesChange).not.toHaveBeenCalled();
|
|
577
|
+
expect(getByTestId("item-0")).toBeDefined();
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
it("should not reorder when readOnly", async () => {
|
|
581
|
+
const reorderSeed: FileEntry[] = [
|
|
582
|
+
{ id: "r-1", file: createMockFile("a.txt", 100, "text/plain"), status: "success" },
|
|
583
|
+
{ id: "r-2", file: createMockFile("b.txt", 100, "text/plain"), status: "success" },
|
|
584
|
+
];
|
|
585
|
+
|
|
586
|
+
const ReadOnlyReorder = () => (
|
|
587
|
+
<FileUploadRoot maxFiles={5} readOnly defaultAcceptedFileEntries={reorderSeed}>
|
|
588
|
+
<FileUploadContext>
|
|
589
|
+
{({ acceptedFileEntries, reorderFileEntry }) => (
|
|
590
|
+
<>
|
|
591
|
+
<ul>
|
|
592
|
+
{acceptedFileEntries.map((f, i) => (
|
|
593
|
+
<li key={f.id} data-testid={`f-${i}`}>
|
|
594
|
+
{f.file.name}
|
|
595
|
+
</li>
|
|
596
|
+
))}
|
|
597
|
+
</ul>
|
|
598
|
+
<button type="button" data-testid="reorder" onClick={() => reorderFileEntry(0, 1)}>
|
|
599
|
+
Reorder
|
|
600
|
+
</button>
|
|
601
|
+
</>
|
|
602
|
+
)}
|
|
603
|
+
</FileUploadContext>
|
|
604
|
+
</FileUploadRoot>
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
const { getByTestId, user } = setUp(<ReadOnlyReorder />);
|
|
608
|
+
|
|
609
|
+
await user.click(getByTestId("reorder"));
|
|
610
|
+
|
|
611
|
+
await waitFor(() => {
|
|
612
|
+
expect(getByTestId("f-0").textContent).toBe("a.txt");
|
|
613
|
+
expect(getByTestId("f-1").textContent).toBe("b.txt");
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it("should still have data-readonly when both disabled and readOnly are set", () => {
|
|
618
|
+
const { getByTestId } = setUp(<BasicFileUpload disabled readOnly />);
|
|
619
|
+
const dropzone = getByTestId("dropzone");
|
|
620
|
+
|
|
621
|
+
expect(dropzone.getAttribute("data-readonly")).toBe("");
|
|
622
|
+
expect(dropzone.getAttribute("data-disabled")).toBe("");
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
|
|
482
626
|
describe("status change", () => {
|
|
483
627
|
it("should call onAcceptedFileEntriesChange when file status changes", async () => {
|
|
484
628
|
const onAcceptedFileEntriesChange = mock(() => {});
|
package/src/useFileUpload.ts
CHANGED
|
@@ -73,7 +73,6 @@ export interface UseFileUploadProps extends UseFileUploadStateProps {
|
|
|
73
73
|
invalid?: boolean;
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
|
-
* NOTE: this currently is a no-op since the UI doesn't have a readOnly state
|
|
77
76
|
* @default false
|
|
78
77
|
*/
|
|
79
78
|
readOnly?: boolean;
|
|
@@ -133,7 +132,7 @@ export function useFileUpload({
|
|
|
133
132
|
|
|
134
133
|
const multiple = maxFiles > 1;
|
|
135
134
|
const maxFilesReached = acceptedFileEntries.length >= maxFiles;
|
|
136
|
-
const triggerDisabled = disabled || maxFilesReached;
|
|
135
|
+
const triggerDisabled = disabled || maxFilesReached || readOnly;
|
|
137
136
|
|
|
138
137
|
const acceptString = Array.isArray(accept) ? accept.join(",") : accept;
|
|
139
138
|
|
|
@@ -231,7 +230,7 @@ export function useFileUpload({
|
|
|
231
230
|
|
|
232
231
|
const setFileEntries = useCallback(
|
|
233
232
|
(files: File[]) => {
|
|
234
|
-
if (disabled) return;
|
|
233
|
+
if (disabled || readOnly) return;
|
|
235
234
|
|
|
236
235
|
const { accepted, rejected } = validateFiles(files);
|
|
237
236
|
|
|
@@ -257,6 +256,7 @@ export function useFileUpload({
|
|
|
257
256
|
},
|
|
258
257
|
[
|
|
259
258
|
disabled,
|
|
259
|
+
readOnly,
|
|
260
260
|
multiple,
|
|
261
261
|
validateFiles,
|
|
262
262
|
setAcceptedFileEntries,
|
|
@@ -268,7 +268,7 @@ export function useFileUpload({
|
|
|
268
268
|
|
|
269
269
|
const reorderFileEntry = useCallback(
|
|
270
270
|
(fromIndex: number, toIndex: number) => {
|
|
271
|
-
if (disabled) return;
|
|
271
|
+
if (disabled || readOnly) return;
|
|
272
272
|
setAcceptedFileEntries((prev) => {
|
|
273
273
|
const files = [...(prev ?? [])];
|
|
274
274
|
|
|
@@ -281,7 +281,7 @@ export function useFileUpload({
|
|
|
281
281
|
return files;
|
|
282
282
|
});
|
|
283
283
|
},
|
|
284
|
-
[disabled, setAcceptedFileEntries],
|
|
284
|
+
[disabled, readOnly, setAcceptedFileEntries],
|
|
285
285
|
);
|
|
286
286
|
|
|
287
287
|
const createFileUrl = useCallback((file: File, callback: (url: string) => void) => {
|
|
@@ -290,20 +290,25 @@ export function useFileUpload({
|
|
|
290
290
|
return () => URL.revokeObjectURL(url);
|
|
291
291
|
}, []);
|
|
292
292
|
|
|
293
|
+
// Root `disabled` intentionally does NOT block removal — disabled trigger should still allow
|
|
294
|
+
// pruning what's already accepted. Root `readOnly` does block it: readOnly preserves the form value.
|
|
293
295
|
const removeFileEntry = useCallback(
|
|
294
296
|
(id: string) => {
|
|
297
|
+
if (readOnly) return;
|
|
295
298
|
setAcceptedFileEntries((prev) => (prev ?? []).filter((f) => f.id !== id));
|
|
296
299
|
},
|
|
297
|
-
[setAcceptedFileEntries],
|
|
300
|
+
[readOnly, setAcceptedFileEntries],
|
|
298
301
|
);
|
|
299
302
|
|
|
300
303
|
const clearFileEntries = useCallback(() => {
|
|
304
|
+
if (readOnly) return;
|
|
301
305
|
setAcceptedFileEntries([]);
|
|
302
|
-
}, [setAcceptedFileEntries]);
|
|
306
|
+
}, [readOnly, setAcceptedFileEntries]);
|
|
303
307
|
|
|
304
308
|
const stateProps = elementProps({
|
|
305
309
|
"data-dragging-over": dataAttr(isDragging),
|
|
306
310
|
"data-disabled": dataAttr(triggerDisabled),
|
|
311
|
+
"data-readonly": dataAttr(readOnly),
|
|
307
312
|
"data-invalid": dataAttr(invalid),
|
|
308
313
|
});
|
|
309
314
|
|
|
@@ -350,6 +355,7 @@ export function useFileUpload({
|
|
|
350
355
|
acceptedFileEntries,
|
|
351
356
|
dragging: isDragging,
|
|
352
357
|
disabled,
|
|
358
|
+
readOnly,
|
|
353
359
|
invalid,
|
|
354
360
|
required,
|
|
355
361
|
maxFiles,
|
|
@@ -435,6 +441,10 @@ export function useFileUpload({
|
|
|
435
441
|
|
|
436
442
|
tabIndex: -1,
|
|
437
443
|
style: visuallyHidden,
|
|
444
|
+
// exclude from the a11y tree — the trigger/dropzone provides the accessible interface,
|
|
445
|
+
// and tabIndex: -1 alone does not stop AT browse-mode cursors from reaching the input.
|
|
446
|
+
// ref: https://github.com/chakra-ui/zag/commit/e847201a3d0b98d3b109d702f9942f0bfa25b97f
|
|
447
|
+
"aria-hidden": true,
|
|
438
448
|
|
|
439
449
|
onChange: (event) => {
|
|
440
450
|
const files = event.target.files;
|
|
@@ -452,12 +462,7 @@ export function useFileUpload({
|
|
|
452
462
|
export type UseFileUploadItemReturn = ReturnType<typeof useFileUploadItem>;
|
|
453
463
|
|
|
454
464
|
export function useFileUploadItem(fileEntry: FileEntry) {
|
|
455
|
-
const { createFileUrl, removeFileEntry, acceptType } = useFileUploadContext();
|
|
456
|
-
|
|
457
|
-
const [isOverlayRendered, setIsOverlayRendered] = useState(false);
|
|
458
|
-
const overlayRef = useCallback((node: HTMLElement | null) => {
|
|
459
|
-
setIsOverlayRendered(!!node);
|
|
460
|
-
}, []);
|
|
465
|
+
const { createFileUrl, removeFileEntry, acceptType, readOnly } = useFileUploadContext();
|
|
461
466
|
|
|
462
467
|
// Image blob URL management
|
|
463
468
|
const [imageSrc, setImageSrc] = useState<string>();
|
|
@@ -469,15 +474,9 @@ export function useFileUploadItem(fileEntry: FileEntry) {
|
|
|
469
474
|
return createFileUrl(fileEntry.file, setImageSrc);
|
|
470
475
|
}, [fileEntry.file, createFileUrl, acceptType]);
|
|
471
476
|
|
|
472
|
-
const overlayStateProps = elementProps({
|
|
473
|
-
"data-has-overlay": dataAttr(isOverlayRendered),
|
|
474
|
-
});
|
|
475
|
-
|
|
476
477
|
return {
|
|
477
478
|
...fileEntry,
|
|
478
479
|
|
|
479
|
-
refs: { overlay: overlayRef },
|
|
480
|
-
|
|
481
480
|
...(acceptType === "image" &&
|
|
482
481
|
imageSrc && {
|
|
483
482
|
imageProps: {
|
|
@@ -486,13 +485,13 @@ export function useFileUploadItem(fileEntry: FileEntry) {
|
|
|
486
485
|
} satisfies React.ImgHTMLAttributes<HTMLImageElement>,
|
|
487
486
|
}),
|
|
488
487
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
// NOTE: `disabled` of item remove button works separately from the overall `disabled` state so we don't have stateProps here
|
|
488
|
+
// Root `disabled` is intentionally NOT propagated here — disabled still allows pruning
|
|
489
|
+
// already-accepted files. Root `readOnly` does block removal so the value is preserved.
|
|
493
490
|
removeButtonProps: buttonProps({
|
|
494
491
|
type: "button",
|
|
492
|
+
disabled: readOnly,
|
|
495
493
|
onClick: () => {
|
|
494
|
+
if (readOnly) return;
|
|
496
495
|
removeFileEntry(fileEntry.id);
|
|
497
496
|
},
|
|
498
497
|
}),
|