UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

323 lines (322 loc) 12.6 kB
"use client"; import _extends from "@babel/runtime-corejs3/helpers/esm/extends"; import _pushInstanceProperty from "core-js-pure/stable/instance/push.js"; 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 === null || value === void 0 ? void 0 : value.some(file => file.errorMessage); if (hasError) { return new FormError('Upload.errorInvalidFiles'); } const hasFiles = (value === null || value === void 0 ? void 0 : value.length) > 0; if (required && (!isChanged && !hasFiles || !hasFiles)) { return error; } return undefined; }, []); const fromInput = useCallback(value => { value === null || value === void 0 || value.forEach((item, index) => { var _item$file; if (!item) { return; } value[index] = item; value[index]['name'] = item['name'] || ((_item$file = item.file) === null || _item$file === void 0 ? void 0 : _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 === null || file === void 0 ? void 0 : file.isLoading) || (file === null || file === void 0 ? void 0 : 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 !== null && externalFile !== void 0 && externalFile.isLoading)) { return externalFile; } const localResolvedFile = localFiles.find(localFile => isSameUploadFile(localFile, externalFile) && !(localFile !== null && localFile !== void 0 && 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 => { var _filesRef$current2; const filesArray = files || []; const existingFileIds = ((_filesRef$current2 = filesRef.current) === null || _filesRef$current2 === void 0 ? void 0 : _filesRef$current2.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 === null || setFieldState === void 0 || setFieldState(fieldIdentifier, 'pending'); setFieldInternals === null || setFieldInternals === void 0 || 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 { _pushInstanceProperty(newFilesLoading).call(newFilesLoading, incomingFileObj); } }); newFilesLoading.forEach((file, index) => { var _filesRef$current3; if (updatedByResponse.has(index)) { return; } const currentFile = (_filesRef$current3 = filesRef.current) === null || _filesRef$current3 === void 0 ? void 0 : _filesRef$current3.find(f => f.id && f.id === file.id || f.file && f.file === file.file); if (currentFile !== null && currentFile !== void 0 && 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 === null || newFilesLoading === void 0 ? void 0 : newFilesLoading.filter(file => file != null)) !== null && _newFilesLoading$filt !== void 0 ? _newFilesLoading$filt : []), ...filesRef.current.slice(indexOfFirstNewFile + newFilesLoading.length)]; setFiles(updatedFiles); handleChange(updatedFiles); } } finally { setFieldState === null || setFieldState === void 0 || 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 === null || existingFiles === void 0 ? void 0 : 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 === null || files === void 0 ? void 0 : 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 !== null && item !== void 0 && item.file && !(item.file instanceof File)) { var _lastModified, _item$file2, _type, _item$file3; item['file'] = new File([], item['name'] || (item === null || item === void 0 ? void 0 : item.file['name']), { lastModified: (_lastModified = (_item$file2 = item.file) === null || _item$file2 === void 0 ? void 0 : _item$file2.lastModified) !== null && _lastModified !== void 0 ? _lastModified : 0, type: (_type = (_item$file3 = item.file) === null || _item$file3 === void 0 ? void 0 : _item$file3.type) !== null && _type !== void 0 ? _type : '' }); } return item; }); } return value; } //# sourceMappingURL=Upload.js.map