Source: FormDataDeep.mjs

/** @module FormDataDeep */

/**
 * Usage: `Array<HTMLElement>.filter(removeChildren)`
 * 
 * Removes elements from the array which are children of other elements in the array.
 */
export function removeChildren(element,index,array){
    for(const sibling of array){
        if(!sibling.isSameNode(element) && sibling.contains(element)){
            return false
        }
    }
    return true
}
/**
 * Appends a new value onto an existing key inside an object, or adds the key if it does not already exist.
 * @param {Object} object 
 * @param {String} name 
 * @param {*} value 
 */
export function append(object,name,value){
    if(name in object){
        if(object[name] instanceof Array){
            object[name].push(value)
        }else{
            object[name]=[formDataDeep[name],value]
        }
    }else{
        object[name]=value
    }
}
/**
 * Get `form` data as an object, including nested forms.
 * @param {HTMLFormElement} form
 * @param {Boolean} primitives whether to convert string values to primitives. Defaults `false`.
 */
export function getFormDataDeep(form,primitives=false){
    const formDataDeep={}
    const formData=new FormData(form)
    for(const name of formData.keys()){
        const value=formData.getAll(name)
        if(value.length==0){
            continue
        }
        if(primitives){
            for(let i=0;i<value.length;i++){
                if(!isNaN(+value[i])){
                    value[i]=+value[i]
                }
                if(value[i]=='on'){
                    value[i]=true
                }
            }
        }
        if(value.length==1){
            formDataDeep[name]=value[0]
        }else{
            formDataDeep[name]=value
        }
    }
    for(const child of [...form.querySelectorAll('form')].filter(removeChildren)){
        const value=getFormDataDeep(child,primitives)
        if(Object.keys(value)==0){
            continue
        }
        const name=child.name
        append(formDataDeep,name,value)
    }
    return formDataDeep
}
/**
 * Convenience class to have a similar interface to FormData
 */
export class FormDataDeep extends FormData {
    /**
     * Get `form` data as an object, including nested forms.
     * @param {HTMLFormElement | Object} form the root form element, or a JSON Object containing form data.
     * @param {Boolean} primitives whether to convert string values to primitives. Defaults `false`.
     */
    constructor(form,primitives=false){
        if(form instanceof HTMLFormElement){
            Object.assign(this,getFormDataDeep(form,primitives))
        }else{
            Object.assign(this,form)
        }
    }
    append(name,value,filename=undefined){
        if(filename!==undefined){
            value=new File([value],filename)
        }
        append(this,name,value)
    }
    delete(name){
        delete this[name]
    }
    entries(){
        return Object.entries(this).filter((value)=>!value instanceof Function)
    }
    get(name){
        if(this[name] instanceof Array){
            return this[name].at(-1)
        }else{
            return this[name]
        }
    }
    getAll(name){
        if(this[name] instanceof Array){
            return this[name]
        }else{
            return [this[name]]
        }
    }
    has(name){
        return name in this
    }
    keys(){
        return this.entries().keys()
    }
    set(name,value,filename=undefined){
        if(filename!==undefined){
            value=new File([value],filename)
        }
        this[name]=value
    }
    values(){
        return this.entries().values()
    }
    /**
     * Applies the values in this object to the target form.
     * @param {HTMLFormElement} form 
     */
    apply(form){
        for(const element of form.elements){
            const name=element.name
            if(! name in this){
                continue
            }
            let value
            if(this[name] instanceof Array){
                value=this[name].shift()
            }else{
                value=this[name]
                delete this[name]
            }
            switch(element.type){
                case 'radio':
                case 'checkbox':{
                    element.checked=true
                    break
                }
                default:{
                    element.value=value
                    break
                }
            }
        }
        for(const child of [...form.querySelectorAll('form')].filter(removeChildren)){
            const name=child.name
            if(! name in this){
                continue
            }
            new FormDataDeep(this[name]).apply(form)
        }
    }
}