import React, { ReactNode, CSSProperties, RefObject, ChangeEvent, DragEvent } from 'react'; import PropTypes from 'prop-types'; import UploadFoundation from '@douyinfe/semi-foundation/lib/es/upload/foundation'; import FileCard from './fileCard'; import BaseComponent from '../_base/baseComponent'; import Cropper from '../cropper'; import { type ModalReactProps } from '../modal'; import type { FileItem, RenderFileItemProps, UploadListType, PromptPositionType, BeforeUploadProps, AfterUploadProps, OnChangeProps, customRequestArgs, CustomError, RenderPictureCloseProps, RenderFileListTitleProps, CropProps } from './interface'; import { Locale } from '../locale/interface'; import '@douyinfe/semi-foundation/lib/es/upload/upload.css'; import type { CustomFile, UploadAdapter, BeforeUploadObjectResult, AfterUploadResult, FileItemStatus } from '@douyinfe/semi-foundation/lib/es/upload/foundation'; import type { ValidateStatus } from '../_base/baseComponent'; import { ShowTooltip } from '../typography'; export type { FileItem, FileItemStatus, RenderFileItemProps, UploadListType, PromptPositionType, BeforeUploadProps, AfterUploadProps, OnChangeProps, customRequestArgs, CustomError, BeforeUploadObjectResult, AfterUploadResult, RenderFileListTitleProps, CropProps, }; export interface UploadProps { accept?: string; action: string; afterUpload?: (object: AfterUploadProps) => AfterUploadResult; beforeUpload?: (object: BeforeUploadProps) => BeforeUploadObjectResult | Promise | boolean; beforeClear?: (fileList: Array) => boolean | Promise; beforeRemove?: (file: FileItem, fileList: Array) => boolean | Promise; capture?: boolean | 'user' | 'environment' | undefined; children?: ReactNode; className?: string; customRequest?: (object: customRequestArgs) => void; data?: Record | ((file: File) => Record); defaultFileList?: Array; directory?: boolean; disabled?: boolean; dragIcon?: ReactNode; dragMainText?: ReactNode; dragSubText?: ReactNode; draggable?: boolean; addOnPasting?: boolean; fileList?: Array; fileName?: string; headers?: Record | ((file: File) => Record); hotSpotLocation?: 'start' | 'end'; itemStyle?: CSSProperties; limit?: number; listType?: UploadListType; maxSize?: number; minSize?: number; multiple?: boolean; name?: string; onAcceptInvalid?: (files: File[]) => void; onChange?: (object: OnChangeProps) => void; onClear?: () => void; onDrop?: (e: Event, files: Array, fileList: Array) => void; onError?: (e: CustomError, file: CustomFile, fileList: Array, xhr: XMLHttpRequest) => void; onPastingError?: (error: Error | PermissionStatus) => void; onExceed?: (fileList: Array) => void; onFileChange?: (files: Array) => void; onOpenFileDialog?: () => void; onPreviewClick?: (fileItem: FileItem) => void; onProgress?: (percent: number, file: CustomFile, fileList: Array) => void; onRemove?: (currentFile: CustomFile, fileList: Array, currentFileItem: FileItem) => void; onRetry?: (fileItem: FileItem) => void; onSizeError?: (file: CustomFile, fileList: Array) => void; onSuccess?: (responseBody: any, file: CustomFile, fileList: Array) => void; previewFile?: (renderFileItemProps: RenderFileItemProps) => ReactNode; prompt?: ReactNode; promptPosition?: PromptPositionType; picHeight?: string | number; picWidth?: string | number; renderFileItem?: (renderFileItemProps: RenderFileItemProps) => ReactNode; renderPicInfo?: (renderFileItemProps: RenderFileItemProps) => ReactNode; renderThumbnail?: (renderFileItemProps: RenderFileItemProps) => ReactNode; renderPicPreviewIcon?: (renderFileItemProps: RenderFileItemProps) => ReactNode; renderPicClose?: (renderPicCloseProps: RenderPictureCloseProps) => ReactNode; renderFileOperation?: (fileItem: RenderFileItemProps) => ReactNode; fileListTitle?: ReactNode | ((props: RenderFileListTitleProps) => ReactNode); showClear?: boolean; showPicInfo?: boolean; showReplace?: boolean; showRetry?: boolean; showUploadList?: boolean; style?: CSSProperties; timeout?: number; transformFile?: (file: File) => FileItem; uploadTrigger?: 'auto' | 'custom'; validateMessage?: ReactNode; validateStatus?: ValidateStatus; withCredentials?: boolean; showTooltip?: boolean | ShowTooltip; /** * Enable image cropping. Pass `true` to use defaults, or a `CropProps` object * to customize aspect ratio, shape, output quality, etc. */ crop?: boolean | CropProps; /** * Callback invoked before opening the crop modal for each image. * Return `false` (or resolve to `false`) to skip cropping and upload directly. */ beforeCrop?: (file: File, fileList: File[]) => boolean | Promise; /** Callback invoked when cropping fails. */ onCropError?: (error: Error) => void; /** Extra props forwarded to the underlying crop Modal. */ cropModalProps?: ModalReactProps; } export interface UploadState { dragAreaStatus: 'default' | 'legal' | 'illegal'; fileList: Array; inputKey: number; localUrls: Array; replaceIdx: number; replaceInputKey: number; cropperVisible: boolean; /** The image file currently being cropped */ cropperFile: File | null; /** Object URL for the image currently being cropped */ cropperSrc: string; /** Remaining image files queued for cropping (cropped one at a time) */ pendingImageFiles: File[]; /** Non-image files that bypass cropping but are uploaded together */ nonImageFiles: File[]; /** Image files already cropped, accumulated until the queue drains */ croppedFiles: File[]; /** Flag to indicate this round was triggered by a replace action */ isReplaceOperation: boolean; } declare class Upload extends BaseComponent { static propTypes: { accept: PropTypes.Requireable; action: PropTypes.Validator; addOnPasting: PropTypes.Requireable; afterUpload: PropTypes.Requireable<(...args: any[]) => any>; beforeClear: PropTypes.Requireable<(...args: any[]) => any>; beforeRemove: PropTypes.Requireable<(...args: any[]) => any>; beforeUpload: PropTypes.Requireable<(...args: any[]) => any>; children: PropTypes.Requireable; className: PropTypes.Requireable; customRequest: PropTypes.Requireable<(...args: any[]) => any>; data: PropTypes.Requireable; defaultFileList: PropTypes.Requireable; directory: PropTypes.Requireable; disabled: PropTypes.Requireable; dragIcon: PropTypes.Requireable; dragMainText: PropTypes.Requireable; dragSubText: PropTypes.Requireable; draggable: PropTypes.Requireable; fileList: PropTypes.Requireable; fileName: PropTypes.Requireable; headers: PropTypes.Requireable; hotSpotLocation: PropTypes.Requireable; itemStyle: PropTypes.Requireable; limit: PropTypes.Requireable; listType: PropTypes.Requireable<"none" | "picture" | "list">; maxSize: PropTypes.Requireable; minSize: PropTypes.Requireable; multiple: PropTypes.Requireable; name: PropTypes.Requireable; onAcceptInvalid: PropTypes.Requireable<(...args: any[]) => any>; onChange: PropTypes.Requireable<(...args: any[]) => any>; onClear: PropTypes.Requireable<(...args: any[]) => any>; onDrop: PropTypes.Requireable<(...args: any[]) => any>; onError: PropTypes.Requireable<(...args: any[]) => any>; onExceed: PropTypes.Requireable<(...args: any[]) => any>; onFileChange: PropTypes.Requireable<(...args: any[]) => any>; onOpenFileDialog: PropTypes.Requireable<(...args: any[]) => any>; onPreviewClick: PropTypes.Requireable<(...args: any[]) => any>; onProgress: PropTypes.Requireable<(...args: any[]) => any>; onRemove: PropTypes.Requireable<(...args: any[]) => any>; onRetry: PropTypes.Requireable<(...args: any[]) => any>; onSizeError: PropTypes.Requireable<(...args: any[]) => any>; onSuccess: PropTypes.Requireable<(...args: any[]) => any>; onPastingError: PropTypes.Requireable<(...args: any[]) => any>; previewFile: PropTypes.Requireable<(...args: any[]) => any>; prompt: PropTypes.Requireable; promptPosition: PropTypes.Requireable<"left" | "right" | "bottom">; picWidth: PropTypes.Requireable>; picHeight: PropTypes.Requireable>; renderFileItem: PropTypes.Requireable<(...args: any[]) => any>; renderPicPreviewIcon: PropTypes.Requireable<(...args: any[]) => any>; renderFileOperation: PropTypes.Requireable<(...args: any[]) => any>; renderPicClose: PropTypes.Requireable<(...args: any[]) => any>; renderPicInfo: PropTypes.Requireable<(...args: any[]) => any>; renderThumbnail: PropTypes.Requireable<(...args: any[]) => any>; fileListTitle: PropTypes.Requireable any)>>; showClear: PropTypes.Requireable; showPicInfo: PropTypes.Requireable; showReplace: PropTypes.Requireable; showRetry: PropTypes.Requireable; showUploadList: PropTypes.Requireable; style: PropTypes.Requireable; timeout: PropTypes.Requireable; transformFile: PropTypes.Requireable<(...args: any[]) => any>; uploadTrigger: PropTypes.Requireable<"auto" | "custom">; validateMessage: PropTypes.Requireable; validateStatus: PropTypes.Requireable<"default" | "error" | "warning" | "success">; withCredentials: PropTypes.Requireable; showTooltip: PropTypes.Requireable>; crop: PropTypes.Requireable>; beforeCrop: PropTypes.Requireable<(...args: any[]) => any>; onCropError: PropTypes.Requireable<(...args: any[]) => any>; cropModalProps: PropTypes.Requireable; }; static defaultProps: Partial; static FileCard: typeof FileCard; constructor(props: UploadProps); /** * Notes: * The input parameter and return value here do not declare the type, otherwise tsc may report an error in form/fields.tsx when wrap after withField * `The types of the parameters "props" and "nextProps" are incompatible. The attribute "action" is missing in the type "Readonly", but it is required in the type "UploadProps".` * which seems to be a bug, remove props type declare here */ static getDerivedStateFromProps(props: any): { fileList: any; }; get adapter(): UploadAdapter; foundation: UploadFoundation; inputRef: RefObject; replaceInputRef: RefObject; cropperRef: RefObject; pastingCb: null | ((params: any) => void); pasteEventCb: null | ((params: any) => void); componentDidMount(): void; componentWillUnmount(): void; onClick: () => void; onChange: (e: ChangeEvent) => void; /** * Check if file is an image */ isImageFile: (file: File) => boolean; /** * Entry point that decides whether incoming files need cropping. * Image files are queued for sequential cropping; non-image files are kept aside * and uploaded together with the cropped results once the queue drains. */ handleCropFiles: (files: File[], isReplaceOperation?: boolean) => Promise; /** * Run the user-provided beforeCrop hook (if any) and return whether cropping should proceed. * Errors fall back to skipping cropping so users are never blocked. */ shouldCropFile: (file: File, files: File[]) => Promise; /** * Forward files to the appropriate foundation handler based on operation type. */ dispatchUpload: (files: File[], isReplaceOperation: boolean) => void; /** * Confirm the current crop. If more images are queued, advance to the next one; * otherwise dispatch the accumulated cropped files together with any non-image files. */ handleCropOk: () => Promise; /** * Cancel the current crop session, revoke object URLs and reset state. */ handleCropCancel: () => void; /** Internal helper that tears down crop state and resets the file input. */ closeCropperAndReset: () => void; replace: (index: number) => void; onReplaceChange: (e: ChangeEvent) => void; clear: () => void; remove: (fileItem: FileItem) => void; /** * ref method * insert files at index * @param files Array * @param index number * @returns */ insert: (files: Array, index?: number) => void; /** * ref method * manual upload by user */ upload: () => void; /** * ref method * manual open file select dialog */ openFileDialog: () => void; renderFile: (file: FileItem, index: number, locale: Locale['Upload']) => ReactNode; renderFileList: () => ReactNode; renderFileListPic: () => React.JSX.Element; renderFileListDefault: () => React.JSX.Element; onDrop: (e: DragEvent) => void; onDragOver: (e: DragEvent) => void; onDragLeave: (e: DragEvent) => void; onDragEnter: (e: DragEvent) => void; renderAddContent: () => string | number | boolean | Iterable | React.JSX.Element; renderDragArea: () => ReactNode; renderCropperModal: () => ReactNode; render(): ReactNode; } export default Upload;