1// fixi.js, github.com/bigskysoftware/fixi (Zero-Clause BSD)2(()=>{3 if(document.__fixi_mo) return;4 document.__fixi_mo = new MutationObserver((recs)=>recs.forEach((r)=>r.type === "childList" && r.addedNodes.forEach((n)=>process(n))))5 let send = (elt, type, detail, bub)=>elt.dispatchEvent(new CustomEvent("fx:" + type, {detail, cancelable:true, bubbles:bub !== false, composed:true}))6 let attr = (elt, name, defaultVal)=>elt.getAttribute(name) || defaultVal7 let dflt = (n, d)=>(window.fixiCfg ?? {})[n] ?? d8 let ignore = (elt)=>elt.closest("[fx-ignore]") != null9 let init = (elt)=>{10 let options = {}11 if (elt.__fixi || ignore(elt) || !send(elt, "init", {options})) return12 elt.__fixi = async(evt)=>{13 let reqs = elt.__fixi.requests ||= new Set()14 let form = elt.form || elt.closest("form")15 let body = new FormData(form ?? undefined, evt.submitter)16 if (elt.name && !evt.submitter && (!form || (elt.form === form && elt.type === 'submit'))) body.append(elt.name, elt.value)17 let ac = new AbortController()18 let cfg = {19 trigger:evt,20 action:attr(elt, "fx-action"),21 method:attr(elt, "fx-method", "GET").toUpperCase(),22 target:document.querySelector(attr(elt, "fx-target")) ?? elt,23 swap:attr(elt, "fx-swap", dflt("swap", "outerHTML")),24 body,25 drop:reqs.size,26 headers:{"FX-Request":"true", ...window.fixiCfg?.headers},27 abort:ac.abort.bind(ac),28 signal:ac.signal,29 preventTrigger:true,30 transition:dflt("transition", document.startViewTransition?.bind(document)),31 fetch:fetch.bind(window)32 }33 let go = send(elt, "config", {cfg, requests:reqs})34 if (cfg.preventTrigger) evt.preventDefault()35 if (!go || cfg.drop) return36 if (/GET|DELETE/.test(cfg.method)){37 let params = new URLSearchParams(cfg.body)38 if (params.size)39 cfg.action += (/\?/.test(cfg.action) ? "&" : "?") + params40 cfg.body = null41 }42 reqs.add(cfg)43 try {44 if (cfg.confirm){45 let result = await cfg.confirm()46 if (!result) return47 }48 if (!send(elt, "before", {cfg, requests:reqs})) return49 cfg.response = await cfg.fetch(cfg.action, cfg)50 cfg.text = await cfg.response.text()51 if (!send(elt, "after", {cfg})) return52 } catch(error) {53 send(elt, "error", {cfg, error})54 return55 } finally {56 reqs.delete(cfg)57 send(elt, "finally", {cfg})58 }59 let doSwap = ()=>{60 if (cfg.swap instanceof Function)61 return cfg.swap(cfg)62 else if (/(before|after)(begin|end)/.test(cfg.swap))63 cfg.target.insertAdjacentHTML(cfg.swap, cfg.text)64 else if(cfg.swap in cfg.target)65 cfg.target[cfg.swap] = cfg.text66 else if(cfg.swap !== 'none') throw cfg.swap67 }68 if (cfg.transition)69 await cfg.transition(doSwap).finished70 else71 await doSwap()72 send(elt, "swapped", {cfg})73 if (!document.contains(elt)) send(document, "swapped", {cfg})74 }75 elt.__fixi.evt = attr(elt, "fx-trigger", elt.matches("form") ? "submit" : elt.matches("input:not([type=button]),select,textarea") ? "change" : "click")76 elt.addEventListener(elt.__fixi.evt, elt.__fixi, options)77 send(elt, "inited", {}, false)78 }79 let process = (n)=>{80 if (n.matches){81 if (ignore(n)) return82 if (n.matches("[fx-action]")) init(n)83 }84 if(n.querySelectorAll) n.querySelectorAll("[fx-action]").forEach(init)85 }86 document.addEventListener("fx:process", (evt)=>process(evt.target))87 document.addEventListener("DOMContentLoaded", ()=>{88 document.__fixi_mo.observe(document.documentElement, {childList:true, subtree:true})89 process(document.body)90 })91})()