diff --git a/dist/index.js b/dist/index.js
index a9b6a96..53d6824 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -2659,7 +2659,13 @@ const getBody = function (method, type, id, formState, dirtyKeys, relationshipNa
const formData = appendToFormData(body, new FormData());
formData.append('meta[files]', JSON.stringify(filenames));
filenames.forEach(filename => {
- formData.append(filename, formState.files[filename]);
+ if (Object.prototype.toString.call(formState.files[filename]) === '[object FileList]') {
+ Array.from(formState.files[filename]).forEach((file, i) => {
+ formData.append(filename + "[" + i + "]", file);
+ });
+ } else {
+ formData.append(filename, formState.files[filename]);
+ }
});
body = formData;
}
diff --git a/dist/index.js.map b/dist/index.js.map
index 06cacaf..13bd2a3 100644
--- a/dist/index.js.map
+++ b/dist/index.js.map
@@ -1 +1 @@
-{"version":3,"file":"index.js","sources":["../src/js/Alert.js","../src/js/Helpers/JsonApiDeserialize.js","../src/js/Helpers/Api.js","../src/js/FormContext.js","../src/js/Error.js","../src/js/Helpers/Options.js","../src/js/Input/Autocomplete.js","../src/js/Input/Checkbox.js","../src/js/Input/CheckboxList.js","../src/js/Input/File.js","../src/js/ConditionalWrapper.js","../src/js/Input.js","../src/js/Input/Password.js","../src/js/Input/Radio.js","../src/js/Input/Search.js","../src/js/Input/Select.js","../src/js/Input/Textarea.js","../src/js/FieldInput.js","../src/js/ExportableInput.js","../src/js/Label.js","../src/js/Field.js","../src/js/FormosaContext.js","../src/js/Helpers/JsonApi.js","../src/js/FormInner.js","../src/js/Form.js","../src/js/FormAlert.js","../src/js/Spinner.js","../src/js/Toast.js","../src/js/ToastContainer.js","../src/js/FormContainer.js","../src/js/Submit.js"],"sourcesContent":["import PropTypes from 'prop-types';\nimport React from 'react'; // eslint-disable-line import/no-unresolved\n\nexport default function Alert({ className, children, type, ...otherProps }) {\n\tif (!children) {\n\t\treturn null;\n\t}\n\n\tlet alertClass = 'formosa-alert';\n\tif (type) {\n\t\talertClass += ` formosa-alert--${type}`;\n\t}\n\tif (className) {\n\t\talertClass += ` ${className}`;\n\t}\n\n\treturn (\n\t\t
\n\t\t\t{children}\n\t\t
\n\t);\n}\n\nAlert.propTypes = {\n\tclassName: PropTypes.string,\n\tchildren: PropTypes.node.isRequired,\n\ttype: PropTypes.string,\n};\n\nAlert.defaultProps = {\n\tclassName: '',\n\ttype: null,\n};\n","const findIncluded = (included, id, type, mainRecord) => {\n\tif (mainRecord && id === mainRecord.id && type === mainRecord.type) {\n\t\tconst output = {\n\t\t\tid: mainRecord.id,\n\t\t\ttype: mainRecord.type,\n\t\t};\n\t\tif (Object.prototype.hasOwnProperty.call(mainRecord, 'attributes')) {\n\t\t\toutput.attributes = mainRecord.attributes;\n\t\t}\n\t\tif (Object.prototype.hasOwnProperty.call(mainRecord, 'meta')) {\n\t\t\toutput.meta = mainRecord.meta;\n\t\t}\n\t\treturn output;\n\t}\n\treturn included.find((data) => (data.id === id && data.type === type));\n};\n\nconst deserializeSingle = (data, otherRows = [], included = [], mainRecord = null) => {\n\tif (!data) {\n\t\treturn data;\n\t}\n\tconst output = {\n\t\tid: data.id,\n\t\ttype: data.type,\n\t\t...data.attributes,\n\t};\n\n\tif (Object.prototype.hasOwnProperty.call(data, 'relationships')) {\n\t\tlet includedRecord;\n\t\tObject.keys(data.relationships).forEach((relationshipName) => {\n\t\t\toutput[relationshipName] = data.relationships[relationshipName].data;\n\t\t\tif (Array.isArray(output[relationshipName])) {\n\t\t\t\toutput[relationshipName].forEach((rel, i) => {\n\t\t\t\t\tincludedRecord = findIncluded(included, rel.id, rel.type, mainRecord);\n\t\t\t\t\tif (includedRecord) {\n\t\t\t\t\t\toutput[relationshipName][i] = deserializeSingle(includedRecord, otherRows, included, mainRecord);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tincludedRecord = findIncluded(otherRows, rel.id, rel.type, mainRecord);\n\t\t\t\t\t\tif (includedRecord) {\n\t\t\t\t\t\t\toutput[relationshipName][i] = deserializeSingle(includedRecord, otherRows, included, mainRecord);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} else if (output[relationshipName] !== null) {\n\t\t\t\tincludedRecord = findIncluded(included, output[relationshipName].id, output[relationshipName].type, mainRecord);\n\t\t\t\tif (includedRecord) {\n\t\t\t\t\toutput[relationshipName] = deserializeSingle(includedRecord, otherRows, included, mainRecord);\n\t\t\t\t} else {\n\t\t\t\t\tincludedRecord = findIncluded(otherRows, output[relationshipName].id, output[relationshipName].type, mainRecord);\n\t\t\t\t\tif (includedRecord) {\n\t\t\t\t\t\toutput[relationshipName] = deserializeSingle(includedRecord, otherRows, included, mainRecord);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\tif (Object.prototype.hasOwnProperty.call(data, 'meta')) {\n\t\toutput.meta = data.meta;\n\t}\n\n\treturn output;\n};\n\nexport const deserialize = (body) => { // eslint-disable-line import/prefer-default-export\n\tif (Array.isArray(body.data)) {\n\t\tconst output = [];\n\t\tbody.data.forEach((data) => {\n\t\t\toutput.push(deserializeSingle(data, body.data, body.included, null));\n\t\t});\n\n\t\tif (Object.prototype.hasOwnProperty.call(body, 'meta')) {\n\t\t\treturn { data: output, meta: body.meta };\n\t\t}\n\n\t\treturn output;\n\t}\n\treturn deserializeSingle(body.data, [], body.included, body.data);\n};\n","import { deserialize } from './JsonApiDeserialize';\nimport { trackPromise } from 'react-promise-tracker';\n\nexport default class Api {\n\tstatic instance() {\n\t\tconst responses = {};\n\t\treturn (url, showSpinner) => {\n\t\t\tif (!Object.prototype.hasOwnProperty.call(responses, url)) {\n\t\t\t\tresponses[url] = Api.get(url, showSpinner);\n\t\t\t}\n\t\t\treturn responses[url];\n\t\t};\n\t}\n\n\tstatic get(url, showSpinner = true) {\n\t\treturn Api.request('GET', url, null, showSpinner);\n\t}\n\n\tstatic delete(url, showSpinner = true) {\n\t\treturn Api.request('DELETE', url, null, showSpinner);\n\t}\n\n\tstatic post(url, body, showSpinner = true) {\n\t\treturn Api.request('POST', url, body, showSpinner);\n\t}\n\n\tstatic put(url, body, showSpinner = true) {\n\t\treturn Api.request('PUT', url, body, showSpinner);\n\t}\n\n\tstatic request(method, url, body = null, showSpinner = true) {\n\t\tconst options = {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\tAccept: 'application/json',\n\t\t\t\t'X-Requested-With': 'XMLHttpRequest',\n\t\t\t},\n\t\t};\n\t\tif (typeof body === 'string') {\n\t\t\toptions.headers['Content-Type'] = 'application/json';\n\t\t}\n\t\tif (Api.getToken()) {\n\t\t\toptions.headers.Authorization = `Bearer ${Api.getToken()}`;\n\t\t}\n\t\tif (body) {\n\t\t\toptions.body = body;\n\t\t}\n\n\t\tlet fullUrl = url;\n\t\tif (process.env.REACT_APP_API_URL && !url.startsWith('http')) {\n\t\t\tfullUrl = `${process.env.REACT_APP_API_URL.replace(/\\/$/, '')}/${url.replace(/^\\//, '')}`;\n\t\t}\n\n\t\tconst event = new CustomEvent('formosaApiRequest', { cancelable: true, detail: { url: fullUrl, options } });\n\t\tif (!document.dispatchEvent(event)) {\n\t\t\treturn Promise.resolve();\n\t\t}\n\n\t\tconst promise = fetch(fullUrl, options)\n\t\t\t.then((response) => {\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\treturn response.json()\n\t\t\t\t\t\t.catch((error) => {\n\t\t\t\t\t\t\tif (error instanceof SyntaxError) {\n\t\t\t\t\t\t\t\tthrow { // eslint-disable-line no-throw-literal\n\t\t\t\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\ttitle: 'Unable to connect to the server. Please try again later.',\n\t\t\t\t\t\t\t\t\t\t\tstatus: '500',\n\t\t\t\t\t\t\t\t\t\t\tdetail: 'The server returned invalid JSON.',\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\tstatus: 500,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.then((json) => {\n\t\t\t\t\t\t\tjson.status = response.status;\n\t\t\t\t\t\t\tthrow json;\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (response.status === 204) {\n\t\t\t\t\treturn {};\n\t\t\t\t}\n\t\t\t\treturn response.json();\n\t\t\t})\n\t\t\t.then((json) => {\n\t\t\t\tif (Object.prototype.hasOwnProperty.call(json, 'data')) {\n\t\t\t\t\treturn deserialize(json);\n\t\t\t\t}\n\t\t\t\treturn json;\n\t\t\t});\n\n\t\treturn showSpinner ? trackPromise(promise) : promise;\n\t}\n\n\tstatic getToken() {\n\t\treturn window.FORMOSA_TOKEN;\n\t}\n\n\tstatic setToken(token) {\n\t\twindow.FORMOSA_TOKEN = token;\n\t}\n\n\tstatic deserialize(json) {\n\t\tif (Object.prototype.hasOwnProperty.call(json, 'data')) {\n\t\t\treturn deserialize(json);\n\t\t}\n\t\treturn json;\n\t}\n}\n","import React from 'react'; // eslint-disable-line import/no-unresolved\n\nexport default React.createContext(\n\t{\n\t\talertClass: '',\n\t\talertText: '',\n\t\terrors: {},\n\t\tfiles: {},\n\t\toriginalRow: {},\n\t\trow: {},\n\t\tresponse: null,\n\t\tsetRow: null,\n\t\ttoastClass: '',\n\t\ttoastText: '',\n\t\tuuid: null,\n\t}\n);\n","import React, { useContext } from 'react'; // eslint-disable-line import/no-unresolved\nimport FormContext from './FormContext';\nimport PropTypes from 'prop-types';\n\nexport default function Error({\n\tid,\n\tname,\n}) {\n\tconst { formState } = useContext(FormContext);\n\tconst hasError = formState && Object.prototype.hasOwnProperty.call(formState.errors, name);\n\n\tconst props = {};\n\tif (name) {\n\t\t// Used for matching the attribute name from the API to this error element.\n\t\tprops['data-name'] = name;\n\t}\n\tif (id || name) {\n\t\tprops.id = `${id || name}-error`;\n\t}\n\n\treturn (\n\t\t\n\t\t\t{hasError && formState.errors[name].map((e) => (
{e}
))}\n\t\t
\n\t);\n}\n\nError.propTypes = {\n\tid: PropTypes.string,\n\tname: PropTypes.string,\n};\n\nError.defaultProps = {\n\tid: null,\n\tname: '',\n};\n","import get from 'get-value';\n\nexport const normalizeOptions = (options, labelKey, valueKey = null) => {\n\tif (!options) {\n\t\treturn [];\n\t}\n\n\tconst output = [];\n\tif (Array.isArray(options)) {\n\t\toptions.forEach((option) => {\n\t\t\tif (typeof option === 'string') {\n\t\t\t\toutput.push({\n\t\t\t\t\tlabel: option,\n\t\t\t\t\tvalue: option,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\toutput.push({\n\t\t\t\t\t...option,\n\t\t\t\t\tlabel: typeof labelKey === 'function' ? labelKey(option) : get(option, labelKey),\n\t\t\t\t\tvalue: typeof valueKey === 'function' ? valueKey(option) : get(option, valueKey),\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t} else {\n\t\tObject.keys(options).forEach((value) => {\n\t\t\tconst option = options[value];\n\t\t\tif (typeof value === 'string') {\n\t\t\t\toutput.push({\n\t\t\t\t\tlabel: option,\n\t\t\t\t\tvalue,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\toutput.push({\n\t\t\t\t\t...option,\n\t\t\t\t\tlabel: typeof labelKey === 'function' ? labelKey(option) : get(option, labelKey),\n\t\t\t\t\tvalue: typeof valueKey === 'function' ? valueKey(option) : get(option, valueKey),\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t}\n\n\treturn output;\n};\n\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping\nexport const escapeRegExp = (string) => (string.replace(/[.*+\\-?^${}()|[\\]\\\\]/g, '\\\\$&'));\n\nconst toSlug = (s) => (\n\ts\n\t\t.normalize('NFD')\n\t\t.replace(/[\\u0300-\\u036f]/g, '')\n\t\t.toLowerCase()\n\t\t.replace(/ & /g, '-and-')\n\t\t.replace(/<[^>]+>/g, '')\n\t\t.replace(/['’.]/g, '')\n\t\t.replace(/[^a-z0-9-]+/g, '-')\n\t\t.replace(/^-+/, '')\n\t\t.replace(/-+$/, '')\n\t\t.replace(/--+/g, '-')\n);\n\nexport const filterByKey = (records, key, value) => {\n\tvalue = value.toLowerCase();\n\tconst escapedValue = escapeRegExp(value);\n\trecords = records.filter((record) => {\n\t\tconst recordValue = get(record, key).toString().toLowerCase() || '';\n\t\treturn recordValue.match(new RegExp(`(^|[^a-z])${escapedValue}`));\n\t});\n\tvalue = toSlug(value);\n\trecords = records.sort((a, b) => {\n\t\tconst aValue = toSlug(get(a, key).toString());\n\t\tconst bValue = toSlug(get(b, key).toString());\n\t\tconst aPos = aValue.indexOf(value) === 0;\n\t\tconst bPos = bValue.indexOf(value) === 0;\n\t\tif ((aPos && bPos) || (!aPos && !bPos)) {\n\t\t\treturn aValue.localeCompare(bValue);\n\t\t}\n\t\tif (aPos && !bPos) {\n\t\t\treturn -1;\n\t\t}\n\t\treturn 1;\n\t});\n\treturn records;\n};\n","import { filterByKey, normalizeOptions } from '../Helpers/Options';\nimport React, { useContext, useEffect, useRef, useState } from 'react'; // eslint-disable-line import/no-unresolved\nimport Api from '../Helpers/Api';\nimport { ReactComponent as CloseIcon } from '../../svg/x.svg';\nimport FormContext from '../FormContext';\nimport get from 'get-value';\nimport PropTypes from 'prop-types';\n\nexport default function Autocomplete({\n\tafterAdd,\n\tafterChange,\n\tclearable,\n\tclearButtonAttributes,\n\tclearButtonClassName,\n\tclearIconAttributes,\n\tclearIconHeight,\n\tclearIconWidth,\n\tclearText,\n\tdisabled,\n\tid,\n\tinputClassName,\n\tinputAttributes,\n\tlabelFn,\n\tlabelKey,\n\tloadingText,\n\tmax,\n\tname,\n\toptionButtonAttributes,\n\toptionButtonClassName,\n\toptionLabelFn,\n\toptionListAttributes,\n\toptionListClassName,\n\toptionListItemAttributes,\n\toptionListItemClassName,\n\toptions,\n\tplaceholder,\n\treadOnly,\n\tremoveButtonAttributes,\n\tremoveButtonClassName,\n\tremoveIconAttributes,\n\tremoveIconHeight,\n\tremoveIconWidth,\n\tremoveText,\n\tsetValue,\n\tshowLoading,\n\turl,\n\tvalue,\n\tvalueKey,\n\tvalueListItemAttributes,\n\twrapperAttributes,\n\twrapperClassName,\n\t...otherProps\n}) {\n\tconst { formState, setValues } = useContext(FormContext);\n\tconst clearButtonRef = useRef(null);\n\tconst inputRef = useRef(null);\n\tconst hiddenInputRef = useRef(null);\n\tconst removeButtonRef = useRef(null);\n\tconst [filter, setFilter] = useState('');\n\tconst [isOpen, setIsOpen] = useState(false);\n\tconst [highlightedIndex, setHighlightedIndex] = useState(0);\n\tconst [optionValues, setOptionValues] = useState(options ? normalizeOptions(options, labelKey, valueKey) : []);\n\tconst [isLoading, setIsLoading] = useState(showLoading || !!url);\n\tconst [loadError, setLoadError] = useState('');\n\tconst api = Api.instance();\n\n\tuseEffect(() => {\n\t\tif (url) {\n\t\t\tapi(url, false)\n\t\t\t\t.catch((error) => {\n\t\t\t\t\tif (Object.prototype.hasOwnProperty.call(error, 'errors')) {\n\t\t\t\t\t\tsetLoadError(error.errors.map((e) => (e.title)).join(' '));\n\t\t\t\t\t\tsetIsLoading(false);\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.then((response) => {\n\t\t\t\t\tif (!response) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tsetOptionValues(normalizeOptions(response, labelKey, valueKey));\n\t\t\t\t\tsetIsLoading(false);\n\t\t\t\t});\n\t\t}\n\t}, [url]);\n\n\tuseEffect(() => {\n\t\tsetOptionValues(options ? normalizeOptions(options, labelKey, valueKey) : []);\n\t}, [options]);\n\n\tuseEffect(() => {\n\t\tsetIsLoading(showLoading);\n\t}, [showLoading]);\n\n\tif (isLoading) {\n\t\treturn ({loadingText}
);\n\t}\n\n\tif (loadError) {\n\t\treturn ({loadError}
);\n\t}\n\n\tlet currentValue = null;\n\tif (setValue !== null) {\n\t\tcurrentValue = value;\n\t} else {\n\t\tif (formState === undefined) {\n\t\t\tthrow new Error(' component must be inside a