{"version":3,"file":"index.js","sources":["../src/helpers/filterFileList.js","../src/data.js","../src/icons.js","../src/helpers/formatBytes.js","../src/FileInfo.js","../src/helpers/removeFile.js","../src/helpers/splitFileList.js","../src/FileDialog.js","../src/Form.js","../src/helpers/ready.js","../src/index.js"],"sourcesContent":["/**\r\n * Filter a FileList returning a new FileList containing all files that pass the callback.\r\n * @param {FileList} files\r\n * @param {(file: File, index: number) => boolean} callback\r\n * @returns {FileList}\r\n */\r\nconst filterFileList = ( files, callback ) => {\r\n if ( files instanceof FileList ) {\r\n const tmp = new DataTransfer();\r\n return Array.from( files ).reduce( ( data, file, index ) => {\r\n if ( callback( file, index ) ) data.items.add( file );\r\n return data;\r\n }, tmp ).files;\r\n }\r\n return null;\r\n};\r\n\r\nexport default filterFileList;","import globalData from \"./FEU_DATA\";\r\n\r\n/**\r\n * The combined result of the default data and global data objects.\r\n */\r\nexport default Object.assign( {\r\n i18n: {\r\n dialogTitle: \"Image Upload\",\r\n dialogCancel: \"Cancel\",\r\n dialogClose: \"Close\",\r\n dialogUpload: \"Upload\",\r\n fileAllTooLarge: \"The image exceeds the maximum size of %s.\",\r\n fileAllTooLargePlural: \"The images exceed the maximum size of %s.\",\r\n fileSomeTooLarge: \"An image exceeded the maximum size of %s.\",\r\n fileSomeTooLargePlural: \"Some images exceeded the maximum size of %s.\",\r\n fileTooMany: \"You can only upload %d image at a time.\",\r\n fileTooManyPlural: \"You can only upload %d images at a time.\",\r\n fileRemove: \"Remove\",\r\n fileName: \"File Name\",\r\n fileType: \"File Type\",\r\n fileSize: \"File Size\",\r\n fileDimensions: \"Dimensions\",\r\n fieldName: \"Name\",\r\n fieldEmail: \"Email\",\r\n fieldCaption: \"Caption\",\r\n fieldDescription: \"Description\",\r\n fieldAlt: \"Alt\",\r\n fieldCustomUrl: \"Custom Url\",\r\n fieldCustomTarget: \"Custom Target\",\r\n fieldTags: \"Tags\",\r\n restUploading: \"Uploading images, please wait...\",\r\n restBadResponse: \"An unexpected response was received from the server.\",\r\n restNetworkError: \"An error occurred uploading the images.\",\r\n resultSucceeded: \"Uploaded\",\r\n resultErrored: \"Errored\",\r\n resultSkipped: \"Skipped\"\r\n },\r\n rest: {\r\n endpoint: null,\r\n nonce: null\r\n },\r\n customTargets: [\r\n { value: \"default\", label: \"Default\" },\r\n { value: \"_blank\", label: \"New Tab (_blank)\" },\r\n { value: \"_self\", label: \"Same Tab (_self)\" }\r\n ]\r\n}, globalData );","const cross = ``;\r\n\r\nconst loader = ``;\r\n\r\nconst warn = ``;\r\n\r\nconst success = ``;\r\n\r\nconst error = ``;\r\n\r\nexport default {\r\n cross,\r\n loader,\r\n warn,\r\n success,\r\n error\r\n};","/**\r\n * Format the supplied `bytes` to a human-readable string.\r\n * @param {number} bytes The value to convert.\r\n * @param {number} [decimals=2] The maximum number of decimals to include in the result string.\r\n * @returns {string} A human-readable representation of the `bytes` value.\r\n */\r\nconst formatBytes = ( bytes, decimals = 2 ) => {\r\n if ( bytes === 0 ) return '0 Bytes';\r\n const k = 1024,\r\n sizes = [ 'Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ],\r\n i = Math.floor( Math.log( bytes ) / Math.log( k ) );\r\n return parseFloat( ( bytes / Math.pow( k, i ) ).toFixed( decimals ) ) + ' ' + sizes[ i ];\r\n};\r\n\r\nexport default formatBytes;","import \"./FileInfo.scss\";\r\n\r\nimport data from \"./data\";\r\nimport formatBytes from \"./helpers/formatBytes\";\r\nimport removeFile from \"./helpers/removeFile\";\r\n\r\n/**\r\n * A map of field ID to a tuple containing 3 values:\r\n * * `string` - The input type for the field i.e. textarea, text, url or select\r\n * * `string` - The name of the i18n string in the data.i18n object to use for the field.\r\n * * `string|undefined` - An optional string for the name of the data property to use for the field. For example a select field requires options.\r\n * @type {Map}\r\n */\r\nconst FieldMap = new Map([\r\n [\"name\", [\"text\", \"fieldName\", undefined, true]],\r\n [\"email\", [\"email\", \"fieldEmail\", undefined, true]],\r\n [\"caption\", [\"textarea\", \"fieldCaption\", undefined, false]],\r\n [\"description\", [\"textarea\", \"fieldDescription\", undefined, false]],\r\n [\"alt\", [\"text\", \"fieldAlt\", undefined, false]],\r\n [\"custom_url\", [\"url\", \"fieldCustomUrl\", undefined, false]],\r\n [\"custom_target\", [\"select\", \"fieldCustomTarget\", \"customTargets\", false]],\r\n [\"tags\", [\"text\", \"fieldTags\", undefined, false]]\r\n]);\r\n\r\n/**\r\n * A class to create and manage information for a single file upload.\r\n */\r\nexport default class FileInfo {\r\n /**\r\n * Create a new instance of the class.\r\n * @param {FileDialog} parent\r\n * @param {File} file\r\n */\r\n constructor( parent, file ) {\r\n // bind the context within internal listeners\r\n this.onRemoveClicked = this.onRemoveClicked.bind( this );\r\n\r\n /**\r\n * The parent `FileDialog` instance.\r\n * @type {FileDialog}\r\n * @readonly\r\n */\r\n this.parentFileDialog = parent;\r\n /**\r\n * The parent `Form` instance.\r\n * @type {Form}\r\n * @readonly\r\n */\r\n this.parentForm = parent.parentForm;\r\n /**\r\n * The `File` object to display information for.\r\n * @type {File}\r\n * @readonly\r\n */\r\n this.file = file;\r\n /**\r\n * The root/outer element for the file information.\r\n * @type {HTMLDivElement}\r\n * @readonly\r\n */\r\n this.element = this.createElement( file );\r\n /**\r\n * The thumbnail image for the file.\r\n * @type {HTMLImageElement}\r\n * @readonly\r\n */\r\n this.thumbnail = this.element.querySelector( \".fg-feu-file-thumbnail\" );\r\n /**\r\n * The remove button for the file.\r\n * @type {HTMLButtonElement}\r\n * @readonly\r\n */\r\n this.buttonRemove = this.element.querySelector( \".fg-feu-file-remove\" );\r\n this.buttonRemove?.addEventListener( \"click\", this.onRemoveClicked, { once: true } );\r\n }\r\n\r\n /**\r\n * Create the root/outer element for the file information.\r\n * @param {File} file\r\n * @return {HTMLDivElement}\r\n */\r\n createElement( file ) {\r\n const element = document.createElement( \"section\" );\r\n element.classList.add( \"fg-feu-file\" );\r\n element.innerHTML = `\r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n`;\r\n\r\n // update the dimensions one the thumbnail is loaded\r\n const dimensions = element.querySelector( \".fg-feu-file-dimensions\" );\r\n const thumb = element.querySelector( \".fg-feu-file-thumbnail\" );\r\n thumb.onload = () => {\r\n thumb.onload = null;\r\n dimensions.textContent = `${ thumb.naturalWidth } x ${ thumb.naturalHeight }`;\r\n }\r\n thumb.src = URL.createObjectURL( file );\r\n\r\n const fields = this.createFieldSetElement();\r\n if ( fields instanceof HTMLFieldSetElement ) element.append( fields );\r\n return element;\r\n }\r\n\r\n /**\r\n * If the parent `Form` has any fields, create them within a HTMLFieldSetElement and return it.\r\n * @return {HTMLFieldSetElement|null} `null` if the parent `Form` has no fields.\r\n */\r\n createFieldSetElement() {\r\n if (!this.parentForm.hasFields) return null;\r\n\r\n const fieldset = document.createElement(\"fieldset\");\r\n fieldset.classList.add(\"fg-feu-file-fields\");\r\n\r\n const isFirstFile = this.parentFileDialog.fileInfos.size === 0;\r\n const fileIndex = this.parentFileDialog.fileInfos.size;\r\n\r\n for (const field of this.parentForm.fields) {\r\n if (!FieldMap.has(field)) continue;\r\n const [type, i18nName, dataName, firstOnly] = FieldMap.get(field);\r\n\r\n let input;\r\n if (firstOnly && !isFirstFile) {\r\n input = document.createElement(\"input\");\r\n input.type = \"hidden\";\r\n input.name = `${field}`;\r\n if (this.parentFileDialog._firstOnlyValues.has(field)) {\r\n input.value = this.parentFileDialog._firstOnlyValues.get(field);\r\n }\r\n } else {\r\n switch (type) {\r\n case \"textarea\":\r\n input = document.createElement(\"textarea\");\r\n input.rows = 3;\r\n break;\r\n case \"text\":\r\n input = document.createElement( \"input\" );\r\n input.type = \"text\";\r\n break;\r\n case \"email\":\r\n input = document.createElement(\"input\");\r\n input.type = type;\r\n break;\r\n case \"url\":\r\n input = document.createElement(\"input\");\r\n input.type = \"url\";\r\n break;\r\n case \"select\":\r\n input = document.createElement(\"select\");\r\n const options = Array.isArray(data[dataName]) ? data[dataName] : [];\r\n for (let i = 0; i < options.length; i++) {\r\n const option = document.createElement(\"option\");\r\n option.value = options[i].value;\r\n option.textContent = options[i].label;\r\n if (i === 0) option.selected = true;\r\n input.append(option);\r\n }\r\n break;\r\n }\r\n\r\n input.classList.add(\"fg-feu-file-field-input\");\r\n input.name = firstOnly ? field : `${field}[${fileIndex}]`;\r\n\r\n if (firstOnly && isFirstFile) {\r\n input.addEventListener(\"input\", (e) => {\r\n this.parentFileDialog.syncFirstOnlyFields(field, e.target.value);\r\n });\r\n }\r\n }\r\n\r\n if (!input) continue;\r\n\r\n if (!firstOnly || isFirstFile) {\r\n const label = document.createElement(\"label\");\r\n label.classList.add(\"fg-feu-file-field\");\r\n\r\n const span = document.createElement(\"span\");\r\n span.classList.add(\"fg-feu-file-field-name\");\r\n span.textContent = data.i18n[i18nName];\r\n\r\n label.append(span, input);\r\n fieldset.append(label);\r\n } else {\r\n fieldset.append(input);\r\n }\r\n }\r\n\r\n return fieldset;\r\n }\r\n\r\n /**\r\n * Clean up any resources and events listeners before removing the element.\r\n */\r\n remove() {\r\n URL.revokeObjectURL( this.thumbnail.src );\r\n this.buttonRemove?.removeEventListener( \"click\", this.onRemoveClicked );\r\n this.element.remove();\r\n this.parentFileDialog.fileInfos.delete( this.element );\r\n }\r\n\r\n /**\r\n * Handles the click event for the remove button.\r\n * @param {MouseEvent} event\r\n * @this {FileInfo}\r\n */\r\n onRemoveClicked( event ) {\r\n event.preventDefault();\r\n const elements = Array.from( this.parentFileDialog.body.querySelectorAll( \".fg-feu-file\" ) );\r\n const index = elements.indexOf( this.element );\r\n if ( index !== -1 && removeFile( this.parentForm.fileInput, index ) ) {\r\n this.remove();\r\n this.parentFileDialog.checkFilesChanged();\r\n }\r\n }\r\n};","import filterFileList from \"./filterFileList\";\r\n\r\n/**\r\n * Remove a file from an `HTMLInputElement.files` property at the given index.\r\n * @param {HTMLInputElement} fileInput\r\n * @param {number} fileIndex\r\n */\r\nconst removeFile = ( fileInput, fileIndex ) => {\r\n if ( fileInput instanceof HTMLInputElement ) {\r\n const current = fileInput.files.length;\r\n if ( current > fileIndex ) {\r\n fileInput.files = filterFileList( fileInput.files, (file, index) => index !== fileIndex );\r\n return fileInput.files.length !== current;\r\n }\r\n }\r\n return false;\r\n};\r\n\r\nexport default removeFile;","/**\r\n * Filter a FileList and return a tuple containing two FileList objects, the first list contains all File items that\r\n * pass the callback, the second contains all items that fail.\r\n * @param {FileList} files\r\n * @param {(file: File, index: number) => boolean} callback\r\n * @returns {[FileList, FileList]}\r\n */\r\nconst splitFileList = ( files, callback ) => {\r\n if ( files instanceof FileList ) {\r\n const pass = new DataTransfer();\r\n const fail = new DataTransfer();\r\n Array.from( files ).forEach( ( file, index ) => {\r\n if ( callback( file, index ) ) pass.items.add( file );\r\n else fail.items.add( file );\r\n } );\r\n return [ pass.files, fail.files ];\r\n }\r\n return [];\r\n};\r\n\r\nexport default splitFileList;","import \"./FileDialog.scss\";\r\n\r\nimport data from \"./data\";\r\nimport icons from \"./icons\";\r\nimport FileInfo from \"./FileInfo\";\r\nimport splitFileList from \"./helpers/splitFileList\";\r\nimport formatBytes from \"./helpers/formatBytes\";\r\n\r\n\r\n/**\r\n * A dialog to display and capture information for files being uploaded.\r\n */\r\nexport default class FileDialog {\r\n /**\r\n * Create a new instance of the class.\r\n * @param {Form} parent The parent `Form` for this instance.\r\n */\r\n constructor( parent ) {\r\n // bind the context within internal listeners\r\n this.onCancelOrCloseClicked = this.onCancelOrCloseClicked.bind( this );\r\n this.onTrapFocusKeyDown = this.onTrapFocusKeyDown.bind( this );\r\n this.onUploadClicked = this.onUploadClicked.bind( this );\r\n\r\n /**\r\n * The parent `Form` for this instance.\r\n * @type {Form}\r\n * @readonly\r\n */\r\n this.parentForm = parent;\r\n\r\n /**\r\n * The root/outer element for the dialog.\r\n * @type {HTMLDivElement|null}\r\n * @readonly\r\n */\r\n this.element = this.createElement();\r\n\r\n /**\r\n * The body element for the dialog.\r\n * @type {HTMLDivElement|null}\r\n * @readonly\r\n */\r\n this.body = this.element.querySelector( \".fg-feu-dialog-body\" );\r\n\r\n /**\r\n *\r\n * @type {HTMLDivElement|null}\r\n * @readonly\r\n */\r\n this.loader = null;\r\n\r\n /**\r\n * The close button for the dialog that appears within the header.\r\n * @type {HTMLButtonElement}\r\n * @readonly\r\n */\r\n this.buttonClose = this.element.querySelector( \".fg-feu-dialog-close\" );\r\n this.buttonClose?.addEventListener( \"click\", this.onCancelOrCloseClicked );\r\n\r\n /**\r\n * The cancel button for the dialog that appears within the footer.\r\n * @type {HTMLButtonElement}\r\n * @readonly\r\n */\r\n this.buttonCancel = this.element.querySelector( \".fg-feu-dialog-cancel\" );\r\n this.buttonCancel?.addEventListener( \"click\", this.onCancelOrCloseClicked );\r\n\r\n /**\r\n * The upload button for the dialog that appears within the footer.\r\n * @type {HTMLButtonElement}\r\n * @readonly\r\n */\r\n this.buttonUpload = this.element.querySelector( \".fg-feu-dialog-upload\" );\r\n if ( typeof data.rest.endpoint === 'string' && typeof data.rest.nonce === 'string' ) {\r\n this.buttonUpload?.addEventListener( \"click\", this.onUploadClicked );\r\n }\r\n\r\n /**\r\n * A map of HTMLDivElements to their associated FileInfo class instance.\r\n * @type {Map}\r\n * @readonly\r\n */\r\n this.fileInfos = new Map();\r\n\r\n /**\r\n * Tracks first-only field values for syncing\r\n * @type {Map}\r\n * @private \r\n */\r\n this._firstOnlyValues = new Map();\r\n\r\n // Bind new method\r\n this.syncFirstOnlyFields = this.syncFirstOnlyFields.bind(this);\r\n }\r\n\r\n /**\r\n * Create the root/outer element for the dialog.\r\n * @return {HTMLDivElement}\r\n */\r\n createElement() {\r\n const element = document.createElement( \"div\" );\r\n element.classList.add( \"fg-feu-dialog\" );\r\n element.role = \"dialog\";\r\n element.innerHTML = `\r\n \r\n \r\n \r\n`;\r\n return element;\r\n }\r\n\r\n createResultNotice( result ) {\r\n const partial = result.errored > 0 || result.skipped > 0;\r\n let icon, className;\r\n if ( result.success === true ) {\r\n icon = partial ? icons.warn : icons.success;\r\n className = partial ? \"fg-feu-warn\" : \"fg-feu-success\";\r\n } else {\r\n icon = icons.error;\r\n className = \"fg-feu-error\";\r\n }\r\n const element = this.createNotice( result.message, className, icon );\r\n if ( partial ) {\r\n const details = this.createResultDetails( result );\r\n element.append( details );\r\n }\r\n return element;\r\n }\r\n\r\n createResultDetails( result ) {\r\n const details = document.createElement( \"div\" );\r\n details.classList.add( \"fg-feu-result-details\" );\r\n const successes = result.files.filter( file => file.status === \"succeeded\" );\r\n if ( successes.length > 0 ) {\r\n const succeeded = this.createResultFilesList( successes, data.i18n.resultSucceeded, \"fg-feu-succeeded\" );\r\n details.append( succeeded );\r\n }\r\n const errors = result.files.filter( file => file.status === \"errored\" );\r\n if ( errors.length > 0 ) {\r\n const errored = this.createResultFilesList( errors, data.i18n.resultErrored, \"fg-feu-errored\" );\r\n details.append( errored );\r\n }\r\n const skips = result.files.filter( file => file.status === \"skipped\" );\r\n if ( skips.length > 0 ) {\r\n const skipped = this.createResultFilesList( skips, data.i18n.resultSkipped, \"fg-feu-skipped\" );\r\n details.append( skipped );\r\n }\r\n return details;\r\n }\r\n\r\n /**\r\n *\r\n * @param {FileResult[]} files\r\n * @param {string} title\r\n * @param {string} className\r\n * @param {string} [icon]\r\n * @return {HTMLDivElement}\r\n */\r\n createResultFilesList( files, title, className, icon = '' ) {\r\n const element = document.createElement( \"div\" );\r\n element.classList.add( \"fg-feu-result-files\", className );\r\n\r\n const header = document.createElement( \"span\" );\r\n header.classList.add( \"fg-feu-result-files-header\" );\r\n header.innerHTML = typeof icon === 'string' && icon.length > 0 ? `${ icon }${ title }` : title;\r\n element.append( header );\r\n\r\n const list = document.createElement( \"ul\" );\r\n list.classList.add( \"fg-feu-result-files-list\" );\r\n for ( const file of files ) {\r\n const li = document.createElement( \"li\" );\r\n li.textContent = `${ file.name }` + ( file.message.length > 0 ? ` - ${ file.message }` : '' );\r\n list.append( li );\r\n }\r\n element.append( list );\r\n\r\n return element;\r\n }\r\n\r\n /**\r\n * Open the dialog.\r\n */\r\n open() {\r\n this.checkHasFields();\r\n this.checkIsLast();\r\n this.populate();\r\n document.documentElement.classList.add( \"fg-feu-dialog-open\" );\r\n this.parentForm.element.append( this.element );\r\n this.trapFocus();\r\n this.setInitialFocus();\r\n }\r\n\r\n /**\r\n * Close the dialog.\r\n */\r\n close() {\r\n this.buttonUpload.classList.remove( \"fg-feu-hidden\" );\r\n this.buttonUpload.disabled = false;\r\n this.buttonCancel.textContent = data.i18n.dialogCancel;\r\n this.empty();\r\n this.parentForm.reset();\r\n this.releaseFocus();\r\n document.documentElement.classList.remove( \"fg-feu-dialog-open\" );\r\n this.element.remove();\r\n }\r\n\r\n /**\r\n * Sync values from first-only fields to all files\r\n * @param {string} field Field name\r\n * @param {string} value Field value \r\n */\r\n syncFirstOnlyFields(field, value) {\r\n this._firstOnlyValues.set(field, value);\r\n // Find all files except first one\r\n const fileInfos = Array.from(this.fileInfos.values());\r\n if (fileInfos.length <= 1) return;\r\n\r\n fileInfos.slice(1).forEach(fileInfo => {\r\n const input = fileInfo.element.querySelector(`input[name=\"${field}\"]`);\r\n if (input) input.value = value;\r\n });\r\n }\r\n\r\n /**\r\n * Populate the dialog with `FileInfo` elements for each `File` in the `Form`.\r\n */\r\n populate() {\r\n this.empty();\r\n const [ passedSize, tooLarge ] = splitFileList( this.parentForm.fileInput.files, file => this.parentForm.maxSize === 0 || file.size < this.parentForm.maxSize );\r\n const [ files, tooMany ] = splitFileList( passedSize, ( file, i ) => this.parentForm.maxImages === 0 || i < this.parentForm.maxImages );\r\n\r\n const notice = this.createOpenNotice( files, tooLarge, tooMany );\r\n if ( notice instanceof HTMLElement ) this.body.append( notice );\r\n\r\n this.parentForm.fileInput.files = files;\r\n this.checkFilesChanged();\r\n\r\n // Clear first-only values when repopulating\r\n this._firstOnlyValues.clear();\r\n\r\n for ( const file of this.parentForm.files ) {\r\n const fi = new FileInfo( this, file );\r\n this.body.append( fi.element );\r\n this.fileInfos.set( fi.element, fi );\r\n }\r\n }\r\n\r\n createNotice( message, className, icon = '', sticky = false ) {\r\n const notice = document.createElement( \"div\" );\r\n notice.classList.add( \"fg-feu-notice\" );\r\n notice.classList.toggle( \"fg-feu-notice-sticky\", sticky );\r\n if ( typeof className === 'string' && className.length > 0 ) {\r\n notice.classList.add( className );\r\n }\r\n if ( Array.isArray( className ) && className.length > 0 ) {\r\n notice.classList.add( ...className );\r\n }\r\n const msg = document.createElement( \"span\" );\r\n msg.classList.add( \"fg-feu-message\" );\r\n if ( typeof icon === 'string' && icon.length > 0 ) {\r\n msg.innerHTML = `${ icon }${ message }`;\r\n } else {\r\n msg.innerHTML = message;\r\n }\r\n notice.append( msg );\r\n return notice;\r\n }\r\n\r\n createOpenNotice( files, tooLarge, tooMany ) {\r\n if ( tooLarge.length > 0 || tooMany.length > 0 ) {\r\n const messages = [];\r\n if ( tooLarge.length > 0 ) {\r\n let tooLargeMessage;\r\n if ( files.length === 0 ) tooLargeMessage = tooLarge.length === 1 ? data.i18n.fileAllTooLarge : data.i18n.fileAllTooLargePlural;\r\n else tooLargeMessage = tooLarge.length === 1 ? data.i18n.fileSomeTooLarge : data.i18n.fileSomeTooLargePlural;\r\n messages.push( tooLargeMessage.replace( \"%s\", formatBytes( this.parentForm.maxSize ) ) );\r\n }\r\n if ( tooMany.length > 0 ) {\r\n const tooManyMessage = this.parentForm.maxImages === 1 ? data.i18n.fileTooMany : data.i18n.fileTooManyPlural;\r\n messages.push( tooManyMessage.replace( \"%d\", String( this.parentForm.maxImages ) ) );\r\n }\r\n if ( messages.length > 0 ) {\r\n return this.createNotice( messages.join( \" \" ), \"fg-feu-warn\", icons.warn, files.length > 0 );\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n /**\r\n * Empty the dialog removing all `FileInfo` elements and leaving the `body` empty.\r\n */\r\n empty(){\r\n for ( const fi of this.fileInfos.values() ) {\r\n fi.remove();\r\n }\r\n this.fileInfos.clear();\r\n this.body.replaceChildren();\r\n }\r\n\r\n showLoader() {\r\n if ( !( this.loader instanceof HTMLDivElement ) ) {\r\n this.loader = this.createNotice( data.i18n.restUploading, [ \"fg-feu-info\", \"fg-feu-loader\" ], icons.loader );\r\n }\r\n this.body.replaceChildren( this.loader );\r\n this.buttonUpload.disabled = true;\r\n this.buttonCancel.disabled = true;\r\n this.buttonClose.disabled = true;\r\n }\r\n\r\n /**\r\n *\r\n * @param {UploadResponse} result\r\n */\r\n showResult( result ) {\r\n const element = this.createResultNotice( result );\r\n this.body.replaceChildren( element );\r\n this.buttonUpload.classList.add( \"fg-feu-hidden\" );\r\n this.buttonClose.disabled = false;\r\n this.buttonCancel.disabled = false;\r\n this.buttonCancel.textContent = data.i18n.dialogClose;\r\n }\r\n\r\n checkFilesChanged() {\r\n this.checkIsLast();\r\n this.buttonUpload.disabled = !this.parentForm.hasAnyFiles;\r\n }\r\n\r\n /**\r\n * Check if there is only a single file to display and toggle the appropriate CSS class.\r\n */\r\n checkIsLast() {\r\n this.element.classList.toggle( \"fg-feu-single\", this.parentForm.hasSingleFile );\r\n }\r\n\r\n /**\r\n * Check if there are no fields to display and toggle the appropriate CSS class.\r\n */\r\n checkHasFields() {\r\n this.element.classList.toggle( \"fg-feu-no-fields\", !this.parentForm.hasFields );\r\n }\r\n\r\n /**\r\n * Handle the click event for both the cancel and close buttons.\r\n * @param {MouseEvent} event\r\n */\r\n onCancelOrCloseClicked( event ) {\r\n event.preventDefault();\r\n this.close();\r\n }\r\n\r\n async onUploadClicked( event ) {\r\n event.preventDefault();\r\n const formData = new FormData( this.parentForm.element );\r\n this.showLoader();\r\n const result = await this.parentForm.upload( formData );\r\n this.showResult( result );\r\n }\r\n\r\n //region Focus Related\r\n\r\n /**\r\n * Sets the initial focus of the dialog to be either the first field input or the upload button.\r\n */\r\n setInitialFocus() {\r\n if ( this.parentForm.hasFields ) {\r\n const first = this.body.querySelector( \".fg-feu-file-field-input\" );\r\n if ( first instanceof HTMLElement ) first.focus();\r\n } else {\r\n this.buttonUpload.focus();\r\n }\r\n }\r\n\r\n /**\r\n * The element that had focus before the dialog.\r\n * @type {Element|null}\r\n * @private\r\n */\r\n #activeElementReset = null;\r\n\r\n /**\r\n * Begins trapping focus changes using the keyboard to only elements within the current dialog.\r\n */\r\n trapFocus() {\r\n this.#activeElementReset = document.activeElement ?? null;\r\n this.element.addEventListener( \"keydown\", this.onTrapFocusKeyDown );\r\n }\r\n\r\n /**\r\n * Releases the trapping of focus changes back to the document.\r\n */\r\n releaseFocus() {\r\n this.element.removeEventListener( \"keydown\", this.onTrapFocusKeyDown );\r\n if ( this.#activeElementReset instanceof HTMLElement ){\r\n this.#activeElementReset.focus();\r\n this.#activeElementReset = null;\r\n }\r\n }\r\n\r\n /**\r\n * Handle the Tab keydown event to ensure the focus remains trapped within the dialog.\r\n * @param {KeyboardEvent} event\r\n */\r\n onTrapFocusKeyDown( event ) {\r\n if ( event.key === \"Tab\" ) {\r\n const boundary = event.shiftKey ? this.buttonClose : this.buttonCancel;\r\n if ( event.target === boundary ) {\r\n event.preventDefault();\r\n const redirect = event.shiftKey ? this.buttonCancel : this.buttonClose;\r\n redirect.focus();\r\n }\r\n }\r\n if ( event.key === \"Escape\" ) {\r\n event.preventDefault();\r\n this.close();\r\n }\r\n }\r\n\r\n //endregion\r\n};","import \"./Form.scss\";\r\n\r\nimport filterFileList from \"./helpers/filterFileList\";\r\nimport FileDialog from \"./FileDialog\";\r\nimport data from \"./data\";\r\nimport splitFileList from \"./helpers/splitFileList\";\r\nimport formatBytes from \"./helpers/formatBytes\";\r\n\r\n/**\r\n * A class to wrap and extend the functionality of the given form to allow for better file uploading.\r\n */\r\nexport default class Form {\r\n /**\r\n * Create a new instance of the class.\r\n * @param {HTMLFormElement} form\r\n */\r\n constructor( form ) {\r\n // bind the context within internal listeners\r\n this.onDragOver = this.onDragOver.bind( this );\r\n this.onDragDrop = this.onDragDrop.bind( this );\r\n this.onFilesChanged = this.onFilesChanged.bind( this );\r\n\r\n /**\r\n * The HTMLFormElement this class wraps and extends the functionality of.\r\n * @type {HTMLFormElement}\r\n * @readonly\r\n */\r\n this.element = form;\r\n /**\r\n *\r\n * @type {HTMLLabelElement}\r\n * @readonly\r\n */\r\n this.dropZone = this.element.querySelector( \".fg-feu-drop-zone\" );\r\n this.dropZone?.addEventListener( \"dragover\", this.onDragOver );\r\n this.dropZone?.addEventListener( \"drop\", this.onDragDrop );\r\n /**\r\n * @type {HTMLInputElement}\r\n * @readonly\r\n */\r\n this.fileInput = this.element.querySelector( \"input[name='foogallery_images[]']\" );\r\n this.fileInput?.addEventListener( \"change\", this.onFilesChanged );\r\n /**\r\n *\r\n * @type {string[]}\r\n * @readonly\r\n */\r\n this.fields = ( this.element.dataset.fields?.split( \" \" ) ?? [] ).filter( f => typeof f === 'string' && f.length > 0 );\r\n /**\r\n *\r\n * @type {number}\r\n * @readonly\r\n */\r\n this.maxSize = parseInt( this.element.dataset.maxSize ?? 0 ) * 1024 * 1024;\r\n /**\r\n *\r\n * @type {number}\r\n * @readonly\r\n */\r\n this.maxImages = parseInt( this.element.dataset.maxImages ?? 0 );\r\n /**\r\n *\r\n * @type {FileDialog|null}\r\n * @readonly\r\n */\r\n this.dialog = null;\r\n }\r\n\r\n /**\r\n *\r\n * @type {boolean}\r\n * @readonly\r\n */\r\n get hasAnyFiles() {\r\n return ( this.fileInput?.files?.length > 0 ) ?? false;\r\n }\r\n\r\n /**\r\n *\r\n * @type {boolean}\r\n * @readonly\r\n */\r\n get hasSingleFile() {\r\n return ( this.fileInput?.files?.length === 1 ) ?? false;\r\n }\r\n\r\n /**\r\n *\r\n * @type {boolean}\r\n * @readonly\r\n */\r\n get hasFields() {\r\n return this.fields.length > 0;\r\n }\r\n\r\n /**\r\n *\r\n * @return {File[]}\r\n * @readonly\r\n */\r\n get files() {\r\n return Array.from( this.fileInput.files ?? [] );\r\n }\r\n\r\n /**\r\n *\r\n * @param {FormData} formData\r\n * @return {Promise}\r\n */\r\n async upload( formData ) {\r\n try {\r\n const response= await fetch( data.rest.endpoint, {\r\n method: \"POST\",\r\n credentials: 'same-origin',\r\n headers: {\r\n // supply this so we have access to logged-in user details within the endpoints PHP code\r\n \"X-WP-Nonce\": data.rest.nonce\r\n },\r\n body: formData\r\n } );\r\n const contentType = response.headers.get(\"content-type\");\r\n if (!contentType || !contentType.includes(\"application/json\")) {\r\n return { success: false, message: data.i18n.restBadResponse };\r\n }\r\n const result = await response.json();\r\n return !response.ok ? Object.assign( result, { success: false } ) : result;\r\n } catch ( err ) {\r\n return { success: false, message: data.i18n.restNetworkError, error: err };\r\n }\r\n }\r\n\r\n /**\r\n * Reset the form back to its empty state.\r\n */\r\n reset() {\r\n // clears any selected files\r\n this.fileInput.value = null;\r\n }\r\n\r\n /**\r\n * Checks if the `fileInput` has any files and triggers the dialog if required.\r\n */\r\n onFilesChanged() {\r\n if ( this.hasAnyFiles ) {\r\n if ( this.dialog === null ) this.dialog = new FileDialog( this );\r\n this.dialog.open();\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @param {DragEvent} event\r\n */\r\n onDragOver( event ) {\r\n if ( event.dataTransfer.items.length > 0 ) {\r\n // signal intent to handle the drop\r\n event.preventDefault();\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @param {DragEvent} event\r\n */\r\n onDragDrop( event ) {\r\n const dropped = filterFileList( event.dataTransfer.files, file => file.type.startsWith( \"image/\" ) );\r\n if ( dropped.length > 0 ) {\r\n // if there are any images, handle the drop\r\n event.preventDefault();\r\n this.fileInput.files = dropped;\r\n this.onFilesChanged();\r\n }\r\n }\r\n};\r\n\r\n//region type-defs\r\n\r\n/**\r\n * @typedef {object} BadResponse\r\n * @property {false} success\r\n * @property {string} message\r\n */\r\n\r\n/**\r\n * @typedef {object} NetworkErrorResponse\r\n * @property {false} success\r\n * @property {string} message\r\n * @property {Error} error\r\n */\r\n\r\n/**\r\n * @typedef {object} WPErrorResponse\r\n * @property {false} success\r\n * @property {string} message\r\n * @property {string|int} code\r\n * @property {object} data\r\n */\r\n\r\n/**\r\n * @typedef {{ status: (\"succeeded\" | \"errored\" | \"skipped\"), name: string, message: string }} FileResult\r\n * @property {(\"succeeded\" | \"errored\" | \"skipped\")} status\r\n * @property {string} name\r\n * @property {string} message\r\n */\r\n\r\n/**\r\n * @typedef {object} SuccessResponse\r\n * @property {true} success\r\n * @property {string} message\r\n * @property {FileResult[]} files\r\n * @property {number} succeeded\r\n * @property {number} errored\r\n * @property {number} skipped\r\n */\r\n\r\n/**\r\n * @typedef {SuccessResponse|WPErrorResponse|BadResponse|NetworkErrorResponse} UploadResponse\r\n */\r\n\r\n//endregion","/**\r\n * Executes the callback once/if the document is loaded.\r\n * @param {() => void} callback\r\n */\r\nconst ready = callback => {\r\n if ( document.readyState === \"complete\" ) callback();\r\n else document.addEventListener( \"DOMContentLoaded\", () => callback(), { once: true } );\r\n};\r\n\r\nexport default ready;","/**\r\n * This is essentially the public interface for the JavaScript.\r\n * Anything exported from this file will be publicly available\r\n */\r\nimport \"./index.scss\";\r\nimport Form from \"./Form\";\r\nimport ready from \"./helpers/ready\";\r\n\r\n/**\r\n *\r\n * @type {Map}\r\n */\r\nconst forms = new Map();\r\n\r\n/**\r\n * Either create or return an existing instance of the `FEUForm` for the `form`.\r\n * @param {HTMLFormElement} form\r\n * @returns {Form|null} `null` if an error occurred during instantiation of the `FEUForm`.\r\n */\r\nconst init = form => {\r\n // exit early if this form has been initialized previously\r\n if ( forms.has( form ) )\r\n return forms.get( form );\r\n\r\n let wrapped = null;\r\n try {\r\n wrapped = new Form( form );\r\n } catch(err) {\r\n console.error( err );\r\n }\r\n forms.set( form, wrapped );\r\n return wrapped;\r\n};\r\n\r\nready( () => {\r\n document.querySelectorAll( \"form.fg-feu-form\" ).forEach( form => {\r\n init( form );\r\n } );\r\n} );"],"names":["filterFileList","files","callback","FileList","tmp","DataTransfer","Array","from","reduce","data","file","index","items","add","Object","assign","i18n","dialogTitle","dialogCancel","dialogClose","dialogUpload","fileAllTooLarge","fileAllTooLargePlural","fileSomeTooLarge","fileSomeTooLargePlural","fileTooMany","fileTooManyPlural","fileRemove","fileName","fileType","fileSize","fileDimensions","fieldName","fieldEmail","fieldCaption","fieldDescription","fieldAlt","fieldCustomUrl","fieldCustomTarget","fieldTags","restUploading","restBadResponse","restNetworkError","resultSucceeded","resultErrored","resultSkipped","rest","endpoint","nonce","customTargets","value","label","globalData","icons","cross","loader","warn","success","error","formatBytes","bytes","decimals","arguments","length","undefined","i","Math","floor","log","parseFloat","pow","toFixed","FieldMap","Map","FileInfo","constructor","parent","this","onRemoveClicked","bind","parentFileDialog","parentForm","element","createElement","thumbnail","querySelector","buttonRemove","addEventListener","once","document","classList","innerHTML","name","type","size","dimensions","thumb","onload","textContent","naturalWidth","naturalHeight","src","URL","createObjectURL","fields","createFieldSetElement","HTMLFieldSetElement","append","hasFields","fieldset","isFirstFile","fileInfos","fileIndex","field","has","i18nName","dataName","firstOnly","get","input","_firstOnlyValues","rows","options","isArray","option","selected","e","syncFirstOnlyFields","target","span","remove","revokeObjectURL","removeEventListener","delete","event","preventDefault","body","querySelectorAll","indexOf","removeFile","fileInput","HTMLInputElement","current","checkFilesChanged","splitFileList","pass","fail","forEach","FileDialog","onCancelOrCloseClicked","onTrapFocusKeyDown","onUploadClicked","buttonClose","buttonCancel","buttonUpload","role","createResultNotice","result","partial","errored","skipped","icon","className","createNotice","message","details","createResultDetails","successes","filter","status","succeeded","createResultFilesList","errors","skips","title","header","list","li","open","checkHasFields","checkIsLast","populate","documentElement","trapFocus","setInitialFocus","close","disabled","empty","reset","releaseFocus","set","values","slice","fileInfo","passedSize","tooLarge","maxSize","tooMany","maxImages","notice","createOpenNotice","HTMLElement","clear","fi","sticky","toggle","msg","messages","tooLargeMessage","push","replace","tooManyMessage","String","join","replaceChildren","showLoader","HTMLDivElement","showResult","hasAnyFiles","hasSingleFile","formData","FormData","upload","first","focus","activeElementReset","activeElement","key","boundary","shiftKey","Form","form","onDragOver","onDragDrop","onFilesChanged","dropZone","dataset","split","f","parseInt","dialog","response","fetch","method","credentials","headers","contentType","includes","json","ok","err","dataTransfer","dropped","startsWith","forms","wrapped","console","init","readyState"],"mappings":"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"}