UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

319 lines (318 loc) 11.1 kB
"use client"; import _extends from "@babel/runtime/helpers/esm/extends"; import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import classnames from 'classnames'; import FieldBlock from "../../FieldBlock/index.js"; import { useFieldProps, usePath, useTranslation as useFormsTranslation } from "../../hooks/index.js"; import Upload from "../../../../components/Upload.js"; import useUpload, { isFileEqual } from "../../../../components/upload/useUpload.js"; import { pickSpacingProps } from "../../../../components/flex/utils.js"; import HelpButtonInline, { HelpButtonInlineContent } from "../../../../components/help-button/HelpButtonInline.js"; import { useTranslation as useSharedTranslation } from "../../../../shared/index.js"; import { FormError } from "../../utils/index.js"; import { useIterateItemNo } from "../../Iterate/ItemNo/useIterateItemNo.js"; function UploadComponent(props) { const sharedTr = useSharedTranslation().Upload; const formsTr = useFormsTranslation().Upload; const errorMessages = useMemo(() => ({ 'Field.errorRequired': formsTr.errorRequired }), [formsTr.errorRequired]); const validateRequired = useCallback((value, { required, isChanged, error }) => { const hasError = value?.some(file => file.errorMessage); if (hasError) { return new FormError('Upload.errorInvalidFiles'); } const hasFiles = value?.length > 0; if (required && (!isChanged && !hasFiles || !hasFiles)) { return error; } return undefined; }, []); const fromInput = useCallback(value => { value?.forEach((item, index) => { if (!item) { return; } value[index] = item; value[index]['name'] = item['name'] || item.file?.name; }); return value; }, []); const preparedProps = { errorMessages, validateRequired, fromInput, toInput: transformFiles, ...props }; const { id, className, width: widthProp = 'stretch', value, label, labelDescription, help, htmlAttributes, disabled, handleChange, handleFocus, handleBlur, fileHandler, onValidationError, dataContext, ...rest } = useFieldProps(preparedProps, { executeOnChangeRegardlessOfError: true }); const { identifier } = usePath({ id, path: props.path, itemPath: props.itemPath }); const { setFieldState, setFieldInternals } = dataContext || {}; const { title = sharedTr.title, text = sharedTr.text, variant = 'normal', acceptedFileTypes = ['pdf', 'png', 'jpg', 'jpeg'], filesAmountLimit = 100, fileMaxSize = 5, skeleton, onFileDelete, onFileClick, download, allowDuplicates, disableDragAndDrop, buttonProps } = rest; const { files, setFiles, clearFiles } = useUpload(id); const labelWithItemNo = useIterateItemNo({ label: label !== null && label !== void 0 ? label : title, labelSuffix: props.labelSuffix, required: props.required }); const filesRef = useRef(); useMemo(() => { filesRef.current = files; }, [files]); const isSameUploadFile = useCallback((fileA, fileB) => { if (!fileA || !fileB) { return false; } if (fileA.id && fileB.id && fileA.id === fileB.id) { return true; } return isFileEqual(fileA.file, fileB.file); }, []); const isPendingOrErrorFile = useCallback(file => { return Boolean(file?.isLoading || file?.errorMessage); }, []); useEffect(() => { return () => { clearFiles(); }; }, [clearFiles]); useEffect(() => { var _filesRef$current; const externalFiles = value !== null && value !== void 0 ? value : []; const localFiles = (_filesRef$current = filesRef.current) !== null && _filesRef$current !== void 0 ? _filesRef$current : []; const mergedExternalFiles = externalFiles.map(externalFile => { if (!externalFile?.isLoading) { return externalFile; } const localResolvedFile = localFiles.find(localFile => isSameUploadFile(localFile, externalFile) && !localFile?.isLoading); return localResolvedFile || externalFile; }); const filesToPreserve = localFiles.filter(localFile => { if (!isPendingOrErrorFile(localFile)) { return false; } return !mergedExternalFiles.some(externalFile => isSameUploadFile(externalFile, localFile)); }); setFiles([...mergedExternalFiles, ...filesToPreserve]); }, [isPendingOrErrorFile, isSameUploadFile, setFiles, value]); const handleChangeAsync = useCallback(async files => { const filesArray = files || []; const existingFileIds = filesRef.current?.map(file => file.id) || []; const existingFiles = filesArray.filter(file => existingFileIds.includes(file.id)); const newFiles = filesArray.filter(file => !existingFileIds.includes(file.id)); const newValidFiles = newFiles.filter(file => !file.errorMessage); if (newValidFiles.length > 0) { const fieldIdentifier = identifier; setFieldState?.(fieldIdentifier, 'pending'); setFieldInternals?.(fieldIdentifier, { enableAsyncMode: true }); try { const newFilesLoading = newFiles.map(file => ({ ...file, isLoading: !file.errorMessage })); setFiles([...filesRef.current, ...newFilesLoading]); const incomingFiles = await fileHandler(newValidFiles); if (!incomingFiles) { setFiles(existingFiles); handleChange(existingFiles); } else { var _newFilesLoading$filt; const updatedByResponse = new Set(); incomingFiles.forEach(file => { const incomingFileObj = { ...file, isLoading: false }; const foundIndex = newFilesLoading.findIndex(newFile => newFile.isLoading); if (foundIndex >= 0) { newFilesLoading[foundIndex] = incomingFileObj; updatedByResponse.add(foundIndex); } else { newFilesLoading.push(incomingFileObj); } }); newFilesLoading.forEach((file, index) => { if (updatedByResponse.has(index)) { return; } const currentFile = filesRef.current?.find(f => f.id && f.id === file.id || f.file && f.file === file.file); if (currentFile?.isLoading) { newFilesLoading[index] = { ...file, isLoading: true }; } }); const indexOfFirstNewFile = filesRef.current.findIndex(({ id }) => id === newFiles[0].id); const updatedFiles = [...filesRef.current.slice(0, indexOfFirstNewFile), ...((_newFilesLoading$filt = newFilesLoading?.filter(file => file != null)) !== null && _newFilesLoading$filt !== void 0 ? _newFilesLoading$filt : []), ...filesRef.current.slice(indexOfFirstNewFile + newFilesLoading.length)]; setFiles(updatedFiles); handleChange(updatedFiles); } } finally { setFieldState?.(fieldIdentifier, undefined); } } else { handleChange(files); } }, [identifier, fileHandler, onValidationError, handleChange, setFieldInternals, setFieldState, setFiles]); const processValidationErrors = useCallback((files, existingFiles) => { var _existingFiles$map; if (!files || !onValidationError) { return files; } const existingFileIds = (_existingFiles$map = existingFiles?.map(file => file.id)) !== null && _existingFiles$map !== void 0 ? _existingFiles$map : []; const newFiles = files.filter(file => !existingFileIds.includes(file.id)); const newInvalidFiles = newFiles.filter(file => file.errorMessage); if (newInvalidFiles.length === 0) { return files; } const processedInvalidFiles = onValidationError(newInvalidFiles) || newInvalidFiles; return files.map(file => { const processedFile = processedInvalidFiles.find(processed => processed.id === file.id || processed.file && processed.file === file.file); return processedFile || file; }); }, [onValidationError]); const changeHandler = useCallback(({ files }) => { let changeValue = files?.length === 0 ? undefined : files; handleBlur(); if (changeValue) { handleFocus(); } changeValue = processValidationErrors(changeValue, filesRef.current); if (fileHandler) { handleChangeAsync(changeValue); } else { handleChange(changeValue); } }, [handleBlur, handleFocus, fileHandler, processValidationErrors, handleChangeAsync, handleChange]); const width = widthProp; const fieldBlockProps = { id, forId: `${id}-input`, labelSrOnly: true, className: classnames('dnb-forms-field-upload', className), width, help: undefined, ...pickSpacingProps(props) }; const usedLabelDescription = labelDescription !== null && labelDescription !== void 0 ? labelDescription : text; return React.createElement(FieldBlock, fieldBlockProps, React.createElement(Upload, _extends({ id: id, variant: variant, acceptedFileTypes: acceptedFileTypes, filesAmountLimit: filesAmountLimit, download: download, allowDuplicates: allowDuplicates, disableDragAndDrop: disableDragAndDrop, buttonProps: buttonProps, disabled: disabled, fileMaxSize: fileMaxSize, skeleton: skeleton, onChange: changeHandler, onFileDelete: onFileDelete, onFileClick: onFileClick, title: help && labelDescription === false ? React.createElement(LabelWithHelpButton, { label: labelWithItemNo, id: id, help: help }) : labelWithItemNo, text: help && (labelDescription !== null && labelDescription !== void 0 ? labelDescription : text) ? React.createElement(LabelWithHelpButton, { label: usedLabelDescription, id: id, help: help }) : usedLabelDescription }, htmlAttributes), help && React.createElement(HelpButtonInlineContent, { contentId: `${id}-help`, help: help, roundedCorner: variant === 'compact' }), props.children)); } function LabelWithHelpButton(props) { const { label, id, help } = props; return React.createElement(React.Fragment, null, label, React.createElement(HelpButtonInline, { contentId: `${id}-help`, left: label ? 'x-small' : false, help: help })); } export default UploadComponent; UploadComponent._supportsSpacingProps = true; export function transformFiles(value) { if (Array.isArray(value)) { if (value.length === 0) { return undefined; } value.map(item => { if (item?.file && !(item.file instanceof File)) { var _lastModified, _type; item['file'] = new File([], item['name'] || item?.file['name'], { lastModified: (_lastModified = item.file?.lastModified) !== null && _lastModified !== void 0 ? _lastModified : 0, type: (_type = item.file?.type) !== null && _type !== void 0 ? _type : '' }); } return item; }); } return value; } //# sourceMappingURL=Upload.js.map