@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.
@@ -129,7 +129,7 @@ function useFileUpload({ accept, disabled = false, required = false, invalid = f
129
129
  const { acceptedFileEntries, isDragging, setAcceptedFileEntries, setIsDragging } = useFileUploadState(props);
130
130
  const multiple = maxFiles > 1;
131
131
  const maxFilesReached = acceptedFileEntries.length >= maxFiles;
132
- const triggerDisabled = disabled || maxFilesReached;
132
+ const triggerDisabled = disabled || maxFilesReached || readOnly;
133
133
  const acceptString = Array.isArray(accept) ? accept.join(",") : accept;
134
134
  const acceptType = getFileAcceptType(accept);
135
135
  const isValidFileType = useCallback((file)=>{
@@ -214,7 +214,7 @@ function useFileUpload({ accept, disabled = false, required = false, invalid = f
214
214
  setAcceptedFileEntries
215
215
  ]);
216
216
  const setFileEntries = useCallback((files)=>{
217
- if (disabled) return;
217
+ if (disabled || readOnly) return;
218
218
  const { accepted, rejected } = validateFiles(files);
219
219
  const acceptedEntries = accepted.map((file)=>({
220
220
  id: `file-${Date.now()}-${++idCounterRef.current}`,
@@ -242,6 +242,7 @@ function useFileUpload({ accept, disabled = false, required = false, invalid = f
242
242
  }
243
243
  }, [
244
244
  disabled,
245
+ readOnly,
245
246
  multiple,
246
247
  validateFiles,
247
248
  setAcceptedFileEntries,
@@ -250,7 +251,7 @@ function useFileUpload({ accept, disabled = false, required = false, invalid = f
250
251
  updateFileEntryStatus
251
252
  ]);
252
253
  const reorderFileEntry = useCallback((fromIndex, toIndex)=>{
253
- if (disabled) return;
254
+ if (disabled || readOnly) return;
254
255
  setAcceptedFileEntries((prev)=>{
255
256
  const files = [
256
257
  ...prev ?? []
@@ -263,6 +264,7 @@ function useFileUpload({ accept, disabled = false, required = false, invalid = f
263
264
  });
264
265
  }, [
265
266
  disabled,
267
+ readOnly,
266
268
  setAcceptedFileEntries
267
269
  ]);
268
270
  const createFileUrl = useCallback((file, callback)=>{
@@ -270,19 +272,26 @@ function useFileUpload({ accept, disabled = false, required = false, invalid = f
270
272
  callback(url);
271
273
  return ()=>URL.revokeObjectURL(url);
272
274
  }, []);
275
+ // Root `disabled` intentionally does NOT block removal — disabled trigger should still allow
276
+ // pruning what's already accepted. Root `readOnly` does block it: readOnly preserves the form value.
273
277
  const removeFileEntry = useCallback((id)=>{
278
+ if (readOnly) return;
274
279
  setAcceptedFileEntries((prev)=>(prev ?? []).filter((f)=>f.id !== id));
275
280
  }, [
281
+ readOnly,
276
282
  setAcceptedFileEntries
277
283
  ]);
278
284
  const clearFileEntries = useCallback(()=>{
285
+ if (readOnly) return;
279
286
  setAcceptedFileEntries([]);
280
287
  }, [
288
+ readOnly,
281
289
  setAcceptedFileEntries
282
290
  ]);
283
291
  const stateProps = elementProps({
284
292
  "data-dragging-over": dataAttr(isDragging),
285
293
  "data-disabled": dataAttr(triggerDisabled),
294
+ "data-readonly": dataAttr(readOnly),
286
295
  "data-invalid": dataAttr(invalid)
287
296
  });
288
297
  useEffect(()=>{
@@ -320,6 +329,7 @@ function useFileUpload({ accept, disabled = false, required = false, invalid = f
320
329
  acceptedFileEntries,
321
330
  dragging: isDragging,
322
331
  disabled,
332
+ readOnly,
323
333
  invalid,
324
334
  required,
325
335
  maxFiles,
@@ -383,6 +393,10 @@ function useFileUpload({ accept, disabled = false, required = false, invalid = f
383
393
  "aria-required": ariaAttr(required),
384
394
  tabIndex: -1,
385
395
  style: visuallyHidden,
396
+ // exclude from the a11y tree — the trigger/dropzone provides the accessible interface,
397
+ // and tabIndex: -1 alone does not stop AT browse-mode cursors from reaching the input.
398
+ // ref: https://github.com/chakra-ui/zag/commit/e847201a3d0b98d3b109d702f9942f0bfa25b97f
399
+ "aria-hidden": true,
386
400
  onChange: (event)=>{
387
401
  const files = event.target.files;
388
402
  if (files) {
@@ -394,11 +408,7 @@ function useFileUpload({ accept, disabled = false, required = false, invalid = f
394
408
  };
395
409
  }
396
410
  function useFileUploadItem(fileEntry) {
397
- const { createFileUrl, removeFileEntry, acceptType } = useFileUploadContext();
398
- const [isOverlayRendered, setIsOverlayRendered] = useState(false);
399
- const overlayRef = useCallback((node)=>{
400
- setIsOverlayRendered(!!node);
401
- }, []);
411
+ const { createFileUrl, removeFileEntry, acceptType, readOnly } = useFileUploadContext();
402
412
  // Image blob URL management
403
413
  const [imageSrc, setImageSrc] = useState();
404
414
  useEffect(()=>{
@@ -410,26 +420,21 @@ function useFileUploadItem(fileEntry) {
410
420
  createFileUrl,
411
421
  acceptType
412
422
  ]);
413
- const overlayStateProps = elementProps({
414
- "data-has-overlay": dataAttr(isOverlayRendered)
415
- });
416
423
  return {
417
424
  ...fileEntry,
418
- refs: {
419
- overlay: overlayRef
420
- },
421
425
  ...acceptType === "image" && imageSrc && {
422
426
  imageProps: {
423
427
  src: imageSrc,
424
428
  alt: fileEntry.file.name
425
429
  }
426
430
  },
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
431
+ // Root `disabled` is intentionally NOT propagated here — disabled still allows pruning
432
+ // already-accepted files. Root `readOnly` does block removal so the value is preserved.
430
433
  removeButtonProps: buttonProps({
431
434
  type: "button",
435
+ disabled: readOnly,
432
436
  onClick: ()=>{
437
+ if (readOnly) return;
433
438
  removeFileEntry(fileEntry.id);
434
439
  }
435
440
  })
@@ -534,29 +539,11 @@ const FileUploadItemImage = /*#__PURE__*/ forwardRef((props, ref)=>{
534
539
  });
535
540
  });
536
541
  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
542
  const FileUploadItemBackdrop = /*#__PURE__*/ forwardRef(({ status, children, ...props }, ref)=>{
556
543
  const entry = useFileUploadItemContext();
557
544
  if (entry.status !== status) return null;
558
545
  return /*#__PURE__*/ jsx(Primitive.div, {
559
- ref: composeRefs(entry.refs.overlay, ref),
546
+ ref: ref,
560
547
  ...props,
561
548
  children: typeof children === "function" ? children(entry) : children
562
549
  });
@@ -566,4 +553,4 @@ const FileUploadContext = (props)=>{
566
553
  return props.children(useFileUploadContext());
567
554
  };
568
555
 
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 };
556
+ export { FileUploadContext as F, FileUploadDropzone as a, FileUploadHiddenInput as b, FileUploadItemBackdrop as c, FileUploadItemImage as d, FileUploadItemName as e, FileUploadItemRemoveButton as f, FileUploadItemSize as g, FileUploadRoot as h, FileUploadTrigger as i, FileUploadItemProvider as j, useFileUploadItemContext as k, useFileUpload as l, useFileUploadItem as m, useFileUploadContext as u };
@@ -129,7 +129,7 @@ function useFileUpload({ accept, disabled = false, required = false, invalid = f
129
129
  const { acceptedFileEntries, isDragging, setAcceptedFileEntries, setIsDragging } = useFileUploadState(props);
130
130
  const multiple = maxFiles > 1;
131
131
  const maxFilesReached = acceptedFileEntries.length >= maxFiles;
132
- const triggerDisabled = disabled || maxFilesReached;
132
+ const triggerDisabled = disabled || maxFilesReached || readOnly;
133
133
  const acceptString = Array.isArray(accept) ? accept.join(",") : accept;
134
134
  const acceptType = getFileAcceptType(accept);
135
135
  const isValidFileType = react.useCallback((file)=>{
@@ -214,7 +214,7 @@ function useFileUpload({ accept, disabled = false, required = false, invalid = f
214
214
  setAcceptedFileEntries
215
215
  ]);
216
216
  const setFileEntries = react.useCallback((files)=>{
217
- if (disabled) return;
217
+ if (disabled || readOnly) return;
218
218
  const { accepted, rejected } = validateFiles(files);
219
219
  const acceptedEntries = accepted.map((file)=>({
220
220
  id: `file-${Date.now()}-${++idCounterRef.current}`,
@@ -242,6 +242,7 @@ function useFileUpload({ accept, disabled = false, required = false, invalid = f
242
242
  }
243
243
  }, [
244
244
  disabled,
245
+ readOnly,
245
246
  multiple,
246
247
  validateFiles,
247
248
  setAcceptedFileEntries,
@@ -250,7 +251,7 @@ function useFileUpload({ accept, disabled = false, required = false, invalid = f
250
251
  updateFileEntryStatus
251
252
  ]);
252
253
  const reorderFileEntry = react.useCallback((fromIndex, toIndex)=>{
253
- if (disabled) return;
254
+ if (disabled || readOnly) return;
254
255
  setAcceptedFileEntries((prev)=>{
255
256
  const files = [
256
257
  ...prev ?? []
@@ -263,6 +264,7 @@ function useFileUpload({ accept, disabled = false, required = false, invalid = f
263
264
  });
264
265
  }, [
265
266
  disabled,
267
+ readOnly,
266
268
  setAcceptedFileEntries
267
269
  ]);
268
270
  const createFileUrl = react.useCallback((file, callback)=>{
@@ -270,19 +272,26 @@ function useFileUpload({ accept, disabled = false, required = false, invalid = f
270
272
  callback(url);
271
273
  return ()=>URL.revokeObjectURL(url);
272
274
  }, []);
275
+ // Root `disabled` intentionally does NOT block removal — disabled trigger should still allow
276
+ // pruning what's already accepted. Root `readOnly` does block it: readOnly preserves the form value.
273
277
  const removeFileEntry = react.useCallback((id)=>{
278
+ if (readOnly) return;
274
279
  setAcceptedFileEntries((prev)=>(prev ?? []).filter((f)=>f.id !== id));
275
280
  }, [
281
+ readOnly,
276
282
  setAcceptedFileEntries
277
283
  ]);
278
284
  const clearFileEntries = react.useCallback(()=>{
285
+ if (readOnly) return;
279
286
  setAcceptedFileEntries([]);
280
287
  }, [
288
+ readOnly,
281
289
  setAcceptedFileEntries
282
290
  ]);
283
291
  const stateProps = domUtils.elementProps({
284
292
  "data-dragging-over": domUtils.dataAttr(isDragging),
285
293
  "data-disabled": domUtils.dataAttr(triggerDisabled),
294
+ "data-readonly": domUtils.dataAttr(readOnly),
286
295
  "data-invalid": domUtils.dataAttr(invalid)
287
296
  });
288
297
  react.useEffect(()=>{
@@ -320,6 +329,7 @@ function useFileUpload({ accept, disabled = false, required = false, invalid = f
320
329
  acceptedFileEntries,
321
330
  dragging: isDragging,
322
331
  disabled,
332
+ readOnly,
323
333
  invalid,
324
334
  required,
325
335
  maxFiles,
@@ -383,6 +393,10 @@ function useFileUpload({ accept, disabled = false, required = false, invalid = f
383
393
  "aria-required": domUtils.ariaAttr(required),
384
394
  tabIndex: -1,
385
395
  style: domUtils.visuallyHidden,
396
+ // exclude from the a11y tree — the trigger/dropzone provides the accessible interface,
397
+ // and tabIndex: -1 alone does not stop AT browse-mode cursors from reaching the input.
398
+ // ref: https://github.com/chakra-ui/zag/commit/e847201a3d0b98d3b109d702f9942f0bfa25b97f
399
+ "aria-hidden": true,
386
400
  onChange: (event)=>{
387
401
  const files = event.target.files;
388
402
  if (files) {
@@ -394,11 +408,7 @@ function useFileUpload({ accept, disabled = false, required = false, invalid = f
394
408
  };
395
409
  }
396
410
  function useFileUploadItem(fileEntry) {
397
- const { createFileUrl, removeFileEntry, acceptType } = useFileUploadContext();
398
- const [isOverlayRendered, setIsOverlayRendered] = react.useState(false);
399
- const overlayRef = react.useCallback((node)=>{
400
- setIsOverlayRendered(!!node);
401
- }, []);
411
+ const { createFileUrl, removeFileEntry, acceptType, readOnly } = useFileUploadContext();
402
412
  // Image blob URL management
403
413
  const [imageSrc, setImageSrc] = react.useState();
404
414
  react.useEffect(()=>{
@@ -410,26 +420,21 @@ function useFileUploadItem(fileEntry) {
410
420
  createFileUrl,
411
421
  acceptType
412
422
  ]);
413
- const overlayStateProps = domUtils.elementProps({
414
- "data-has-overlay": domUtils.dataAttr(isOverlayRendered)
415
- });
416
423
  return {
417
424
  ...fileEntry,
418
- refs: {
419
- overlay: overlayRef
420
- },
421
425
  ...acceptType === "image" && imageSrc && {
422
426
  imageProps: {
423
427
  src: imageSrc,
424
428
  alt: fileEntry.file.name
425
429
  }
426
430
  },
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
431
+ // Root `disabled` is intentionally NOT propagated here — disabled still allows pruning
432
+ // already-accepted files. Root `readOnly` does block removal so the value is preserved.
430
433
  removeButtonProps: domUtils.buttonProps({
431
434
  type: "button",
435
+ disabled: readOnly,
432
436
  onClick: ()=>{
437
+ if (readOnly) return;
433
438
  removeFileEntry(fileEntry.id);
434
439
  }
435
440
  })
@@ -534,29 +539,11 @@ const FileUploadItemImage = /*#__PURE__*/ react.forwardRef((props, ref)=>{
534
539
  });
535
540
  });
536
541
  FileUploadItemImage.displayName = "FileUploadItemImage";
537
- const FileUploadItemThumbnail = /*#__PURE__*/ react.forwardRef((props, ref)=>{
538
- const { thumbnailProps } = useFileUploadItemContext();
539
- const mergedProps = domUtils.mergeProps(thumbnailProps, props);
540
- return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.div, {
541
- ref: ref,
542
- ...mergedProps
543
- });
544
- });
545
- FileUploadItemThumbnail.displayName = "FileUploadItemThumbnail";
546
- const FileUploadItemMetadata = /*#__PURE__*/ react.forwardRef((props, ref)=>{
547
- const { metadataProps } = useFileUploadItemContext();
548
- const mergedProps = domUtils.mergeProps(metadataProps, props);
549
- return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.div, {
550
- ref: ref,
551
- ...mergedProps
552
- });
553
- });
554
- FileUploadItemMetadata.displayName = "FileUploadItemMetadata";
555
542
  const FileUploadItemBackdrop = /*#__PURE__*/ react.forwardRef(({ status, children, ...props }, ref)=>{
556
543
  const entry = useFileUploadItemContext();
557
544
  if (entry.status !== status) return null;
558
545
  return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.div, {
559
- ref: reactComposeRefs.composeRefs(entry.refs.overlay, ref),
546
+ ref: ref,
560
547
  ...props,
561
548
  children: typeof children === "function" ? children(entry) : children
562
549
  });
@@ -571,12 +558,10 @@ exports.FileUploadDropzone = FileUploadDropzone;
571
558
  exports.FileUploadHiddenInput = FileUploadHiddenInput;
572
559
  exports.FileUploadItemBackdrop = FileUploadItemBackdrop;
573
560
  exports.FileUploadItemImage = FileUploadItemImage;
574
- exports.FileUploadItemMetadata = FileUploadItemMetadata;
575
561
  exports.FileUploadItemName = FileUploadItemName;
576
562
  exports.FileUploadItemProvider = FileUploadItemProvider;
577
563
  exports.FileUploadItemRemoveButton = FileUploadItemRemoveButton;
578
564
  exports.FileUploadItemSize = FileUploadItemSize;
579
- exports.FileUploadItemThumbnail = FileUploadItemThumbnail;
580
565
  exports.FileUploadRoot = FileUploadRoot;
581
566
  exports.FileUploadTrigger = FileUploadTrigger;
582
567
  exports.useFileUpload = useFileUpload;
package/lib/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- var FileUpload12s = require('./FileUpload-12s-1EmFGBYg.cjs');
1
+ var FileUpload12s = require('./FileUpload-12s-nKKpZreT.cjs');
2
2
 
3
3
  var FileUpload_namespace = {
4
4
  __proto__: null,
@@ -7,11 +7,9 @@ var FileUpload_namespace = {
7
7
  HiddenInput: FileUpload12s.FileUploadHiddenInput,
8
8
  ItemBackdrop: FileUpload12s.FileUploadItemBackdrop,
9
9
  ItemImage: FileUpload12s.FileUploadItemImage,
10
- ItemMetadata: FileUpload12s.FileUploadItemMetadata,
11
10
  ItemName: FileUpload12s.FileUploadItemName,
12
11
  ItemRemoveButton: FileUpload12s.FileUploadItemRemoveButton,
13
12
  ItemSize: FileUpload12s.FileUploadItemSize,
14
- ItemThumbnail: FileUpload12s.FileUploadItemThumbnail,
15
13
  Root: FileUpload12s.FileUploadRoot,
16
14
  Trigger: FileUpload12s.FileUploadTrigger
17
15
  };
@@ -44,12 +42,10 @@ exports.FileUploadDropzone = FileUpload12s.FileUploadDropzone;
44
42
  exports.FileUploadHiddenInput = FileUpload12s.FileUploadHiddenInput;
45
43
  exports.FileUploadItemBackdrop = FileUpload12s.FileUploadItemBackdrop;
46
44
  exports.FileUploadItemImage = FileUpload12s.FileUploadItemImage;
47
- exports.FileUploadItemMetadata = FileUpload12s.FileUploadItemMetadata;
48
45
  exports.FileUploadItemName = FileUpload12s.FileUploadItemName;
49
46
  exports.FileUploadItemProvider = FileUpload12s.FileUploadItemProvider;
50
47
  exports.FileUploadItemRemoveButton = FileUpload12s.FileUploadItemRemoveButton;
51
48
  exports.FileUploadItemSize = FileUpload12s.FileUploadItemSize;
52
- exports.FileUploadItemThumbnail = FileUpload12s.FileUploadItemThumbnail;
53
49
  exports.FileUploadRoot = FileUpload12s.FileUploadRoot;
54
50
  exports.FileUploadTrigger = FileUpload12s.FileUploadTrigger;
55
51
  exports.useFileUpload = FileUpload12s.useFileUpload;