diff --git a/README.md b/README.md index 7cbe34c..d9b5c94 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,8 @@ forms with step validation. Save and resume. Pre-fill from the URL, the logged-i user or the date. Unique-value validation. **Anti-spam without a captcha** — honeypot, signed time trap, per-address rate -limit, word blocklist, and Akismet when the site already has it. Nothing asks the +limit, word blocklist, and Akismet — off by default — when the site already has +it and switches it on per form. Nothing asks the visitor to prove they are human, because that charges them for the site's spam problem and fails WCAG for a good number of people. A submission judged spam is **stored**, in a spam status, never silently discarded. diff --git a/assets/js/builder.js b/assets/js/builder.js index ace83dc..1f1e1c4 100644 --- a/assets/js/builder.js +++ b/assets/js/builder.js @@ -8196,10 +8196,14 @@ ${decls} "One per line." ), checkbox( - "Use Akismet when it is installed", + "Check submissions with Akismet", settings.spam.akismet, (value) => set("spam.akismet", value) ), + el("p", { + class: "atfb-hint", + text: "Off unless you switch it on. When on, each submission — the answers, plus the sender’s IP address and user agent — is sent to Akismet’s servers for a spam verdict. Needs the Akismet plugin installed and configured." + }), checkbox( "Ask a simple sum before sending", settings.spam.challenge, diff --git a/assets/js/builder.min.js b/assets/js/builder.min.js index 79d97f2..1f3f2cb 100644 --- a/assets/js/builder.min.js +++ b/assets/js/builder.min.js @@ -1,5 +1,5 @@ -var allTerrainFormsBuilder=function(ee){"use strict";class Zt{constructor(){this.targets=[],this.active=null,this.lastEndMs=0}start(e){if(this.active||e.origin.button!==0)return null;const{payload:t,origin:s}=e,a=s.clientX,i=s.clientY;let o=!1,l=!1,c=null,d=null,h=0,w=0;const _=()=>{document.removeEventListener("pointermove",x),document.removeEventListener("pointerup",I),document.removeEventListener("pointercancel",S),document.removeEventListener("keydown",H),window.removeEventListener("blur",S),c?.remove(),c=null,t.source.classList.remove("atf-is-dragging"),document.body.classList.remove("atf-drag-active"),d?.onLeave?.(b),d=null,this.active=null,this.lastEndMs=Date.now()},u=(E,f)=>{document.dispatchEvent(new CustomEvent(E,{detail:{payload:t,clientX:f?.clientX,clientY:f?.clientY}}))},b={payload:t,isFinished:()=>l,cancel:(E="caller")=>{l||(l=!0,_(),o&&u("os.drag.end"),e.onCancel?.(E))}},g=E=>{o=!0,t.source.classList.add("atf-is-dragging"),document.body.classList.add("atf-drag-active");const f=t.source.getBoundingClientRect();h=t.ghost?.offsetX??a-f.left,w=t.ghost?.offsetY??i-f.top,c=t.ghost?.element??t.source.cloneNode(!0),c.classList.add("atf-drag-ghost"),c.style.width=`${f.width}px`,document.body.appendChild(c),p(E),u("os.drag.start",E)},p=E=>{c&&(c.style.transform=`translate3d(${E.clientX-h}px, ${E.clientY-w}px, 0)`)},x=E=>{if(l)return;if(!o){if(Math.hypot(E.clientX-a,E.clientY-i)<4)return;g(E)}p(E);const f=this.hitTest(E.clientX,E.clientY);f!==d&&(d?.onLeave?.(b),d=f,d?.onEnter?.(b)),u("os.drag.move",E)},I=E=>{if(l)return;if(!o){l=!0,_(),e.onClickOnly?.();return}const f=d;if(l=!0,_(),f&&f.accept(t)){e.onCommit?.(f),f.onDrop(b,{clientX:E.clientX,clientY:E.clientY}),u("os.drag.end",E);return}u("os.drag.end",E),e.onCancel?.(f?"rejected":"no-target")},S=()=>b.cancel("pointercancel"),H=E=>{E.key==="Escape"&&b.cancel("escape")};return document.addEventListener("pointermove",x),document.addEventListener("pointerup",I),document.addEventListener("pointercancel",S),document.addEventListener("keydown",H),window.addEventListener("blur",S),this.active=b,b}registerDropTarget(e){return this.targets=this.targets.filter(t=>t.id!==e.id),this.targets.push(e),()=>{this.targets=this.targets.filter(t=>t.id!==e.id)}}isDragging(){return this.active!==null}recentlyEndedDrag(e=500){return Date.now()-this.lastEndMso.right||to.bottom)continue;const l=Kt(i.element);if(l>a){s=i,a=l;continue}l===a&&s&&Vt(i.element,s.element)&&(s=i)}return s}}function Kt(n){let e=0,t=n;for(;t;)e++,t=t.parentElement;return e}function Vt(n,e){return(e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_FOLLOWING)!==0}function Se(){return window.wp?.os??null}let Le=null;function Z(){const n=Se();return n?.dragManager?n.dragManager:(Le||(Le=new Zt),Le)}function Jt(n){const e=i=>{const o=i.detail?.payload;return o&&n.includes(o.type)?o.source:null},t=i=>i.preventDefault(),s=i=>{const o=e(i);o&&(o.classList.add("atf-is-dragging"),document.body.classList.add("atf-drag-active"),window.getSelection()?.removeAllRanges(),document.addEventListener("selectstart",t))},a=i=>{const o=e(i);o&&(o.classList.remove("atf-is-dragging"),document.body.classList.remove("atf-drag-active"),document.removeEventListener("selectstart",t))};return document.addEventListener("os.drag.start",s),document.addEventListener("os.drag.end",a),()=>{document.removeEventListener("os.drag.start",s),document.removeEventListener("os.drag.end",a),document.removeEventListener("selectstart",t)}}const Qt=["atfb","atfe","atfs","atfm"];function Ee(n,e,t,s,a){const i=e.getBoundingClientRect();if(!a){const o=Qt.find(l=>e.closest(`.${l}`));if(o){const l=e.cloneNode(!0);l.style.transition="",l.style.transform="",a=document.createElement("div"),a.className=`${o} atf-ghost-scope`,a.appendChild(l)}}return a&&(a.style.width=`${Math.round(i.width)}px`,a.style.maxWidth=`${Math.round(i.width)}px`,a.style.boxSizing="border-box"),{type:n,source:e,data:t,ghost:{element:a,offsetX:s.clientX-i.left,offsetY:s.clientY-i.top,hint:{neutral:"",accept:"",reject:"",hidden:!0}}}}function Je(n,e,t,s){const a=Array.from(n.querySelectorAll(e)).filter(i=>i!==s&&!i.classList.contains("atf-drag-ghost"));for(let i=0;ia.fetch(l,c,{source:"allterrain-forms"}):(l,c)=>fetch(l,c))(t,{credentials:"same-origin",...e,headers:s});if(!o.ok){let l=`Request failed with status ${o.status}.`,c="";try{const d=await o.json();l=d.message??l,c=d.code??""}catch{}throw new de(l,o.status,c)}if(o.status!==204)return await o.json()}const U=n=>Ce(n);async function es(n){if(!W?.wpRestUrl)throw new de("AllTerrain Forms is not configured on this page.",0);const e=W.nonce?{"X-WP-Nonce":W.nonce}:{},t=Se(),s=Qe(W.wpRestUrl,n),a=t?.fetch?await t.fetch(s,{credentials:"same-origin",headers:e},{source:"allterrain-forms"}):await fetch(s,{credentials:"same-origin",headers:e});if(!a.ok)throw new de(`Request failed with status ${a.status}.`,a.status);return await a.json()}function ts(n){if(!n||!n.includes("&"))return n;const e=document.createElement("textarea");return e.innerHTML=n,e.value}const K=(n,e)=>Ce(n,{method:"POST",body:JSON.stringify(e)}),he=n=>Ce(n,{method:"DELETE"});function et(n){const e=new URLSearchParams;for(const[s,a]of Object.entries(n))a===void 0||a===""||a===!1||e.set(s,String(a));const t=e.toString();return t?`?${t}`:""}function ue(n){const e=n?.schema?.settings;return e&&(!e.themeOverrides||Array.isArray(e.themeOverrides))&&(e.themeOverrides={...e.themeOverrides}),n}const F={config:()=>U("/config"),mailpoet:()=>U("/mailpoet"),listForms:()=>U("/forms"),listArchivedForms:()=>U("/forms?archived=1"),archiveForm:n=>K(`/forms/${n}/archive`,{}),unarchiveForm:n=>K(`/forms/${n}/unarchive`,{}),getForm:n=>U(`/forms/${n}`).then(ue),createForm:n=>K("/forms",n).then(ue),updateForm:(n,e)=>K(`/forms/${n}`,e).then(ue),duplicateForm:n=>K(`/forms/${n}/duplicate`,{}).then(ue),deleteForm:n=>he(`/forms/${n}`),preview:(n,e)=>K(`/forms/${n}/preview`,e),pages:()=>es("wp/v2/pages?per_page=100&status=publish&orderby=title&order=asc&_fields=id,title").then(n=>n.map(e=>({id:e.id,title:ts(e.title?.rendered??"")||`#${e.id}`}))),mergeTags:n=>U(`/forms/${n}/merge-tags`).then(e=>e.groups),analytics:(n,e="")=>U(`/forms/${n}/analytics${e?`?dimension=${encodeURIComponent(e)}`:""}`),demoStatus:()=>U("/demo"),demoSeed:n=>K("/demo",n?{count:n}:{}),demoRemove:()=>he("/demo"),listEntries:n=>U(`/entries${et(n)}`),getEntry:n=>U(`/entries/${n}`),updateEntry:(n,e)=>K(`/entries/${n}`,e),deleteEntry:n=>he(`/entries/${n}`),exportEntries:n=>U(`/entries/export${et(n)}`),listThemes:()=>U("/themes"),saveTheme:n=>K("/themes",n),deleteTheme:n=>he(`/themes/${n}`)};function r(n,e={}){const t=document.createElement(n);e.class&&(t.className=e.class),e.text!==void 0&&(t.textContent=e.text),e.html!==void 0&&(t.innerHTML=e.html),e.title&&(t.title=e.title),e.type&&"type"in t&&(t.type=e.type),e.value!==void 0&&"value"in t&&(t.value=e.value),e.placeholder!==void 0&&"placeholder"in t&&(t.placeholder=e.placeholder),e.href&&"href"in t&&(t.href=e.href);for(const[s,a]of Object.entries(e.attrs??{}))a===void 0||a===!1||t.setAttribute(s,a===!0?"":String(a));Object.assign(t.style,e.style??{});for(const[s,a]of Object.entries(e.on??{}))t.addEventListener(s,a);for(const s of e.children??[])s==null||s===!1||t.append(s);return t}function R(n){n.replaceChildren()}function z(n){return r("span",{class:`dashicons ${n.startsWith("dashicons-")?n:`dashicons-${n}`}`,attrs:{"aria-hidden":"true"}})}function y(n,e,t){const s=e.matches("input, select, textarea, button")?e:e.querySelector("input, select, textarea");if(s){const a=s.id||`atf-c-${Math.random().toString(36).slice(2,9)}`;return s.id=a,r("div",{class:"atfb-row",children:[r("label",{class:"atfb-row__label",text:n,attrs:{for:a}}),e,t?r("p",{class:"atfb-row__hint",text:t}):null]})}return r("div",{class:"atfb-row",children:[r("span",{class:"atfb-row__label",text:n}),e,t?r("p",{class:"atfb-row__hint",text:t}):null]})}function T(n,e,t=""){return r("input",{class:"atfb-input",type:"text",value:n,placeholder:t,on:{input:s=>e(s.target.value)}})}function G(n,e){if(Y("os-number-field")){const t=document.createElement("os-number-field");return t.setAttribute("value",n),t.classList.add("atfb-field"),t.addEventListener("os-input-change",s=>{e(String(s.detail?.value??""))}),t}return r("input",{class:"atfb-input",type:"number",value:n,on:{input:t=>e(t.target.value)}})}function pe(n,e,t=4){const s=r("textarea",{class:"atfb-input atfb-input--area",attrs:{rows:t},on:{input:a=>e(a.target.value)}});return s.value=n,s}function D(n,e,t){if(Y("os-select")&&Y("os-option")){const s=document.createElement("os-select");s.setAttribute("value",n),s.classList.add("atfb-field");for(const a of e){const i=document.createElement("os-option");i.setAttribute("value",a.value),i.textContent=a.label,s.append(i)}return s.addEventListener("os-pick",a=>{t(String(a.detail?.value??""))}),s}return r("select",{class:"atfb-input atfb-select",on:{change:s=>t(s.target.value)},children:e.map(s=>r("option",{value:s.value,text:s.label,attrs:{selected:s.value===n}}))})}function O(n,e,t){if(Y("os-checkbox-label")){const a=document.createElement("os-checkbox-label");return a.setAttribute("label",n),a.classList.add("atfb-check"),e&&a.setAttribute("checked",""),a.addEventListener("os-checkbox-change",i=>{t(!!i.detail?.checked)}),a}const s=r("input",{type:"checkbox",on:{change:a=>t(a.target.checked)}});return s.checked=e,r("label",{class:"atfb-check",children:[s,r("span",{text:n})]})}function $(n,e,t="secondary",s){const a=[s?z(s):null,r("span",{text:n})];if(Y("os-button")){const i=document.createElement("os-button");i.setAttribute("variant",t),i.setAttribute("type","button"),i.classList.add("atfb-button",`atfb-button--${t}`),i.addEventListener("click",e);for(const o of a)o&&i.append(o);return i}return r("button",{class:`atfb-button atfb-button--${t}`,type:"button",on:{click:e},children:a})}function Y(n){return typeof customElements<"u"&&!!customElements.get(n)}const ss=["os-button","os-checkbox-label","os-number-field","os-select","os-option","os-segmented","os-segment","os-color-field","os-empty-state"];let fe=null;function ns(){if(fe)return fe;const n=window.wp?.os;return fe=n?.loadComponents?n.loadComponents(ss).catch(()=>{}):Promise.resolve(),fe}async function ie(n,e=""){const t=window.wp?.os;return t?.confirm?t.confirm({title:e,message:n,danger:!0}):window.confirm(n)}function N(n,e="",t="info"){const s=window.wp?.os;if(s?.notify){s.notify({title:n,body:e,type:t});return}console.info(`[AllTerrain Forms] ${n}${e?`: ${e}`:""}`)}function as(n){let e=0,t;return(...s)=>{t=s,!e&&(e=window.requestAnimationFrame(()=>{e=0,n(...t)}))}}function $e(n,e){let t=0;return(...s)=>{window.clearTimeout(t),t=window.setTimeout(()=>n(...s),e)}}function is(n){try{return window.localStorage.getItem(n)??""}catch{return""}}function rs(n,e){try{window.localStorage.setItem(n,e)}catch{}}function Ae(n){const e=n.closest(".os-window__body");e&&(e.addEventListener("scroll",()=>{e.scrollTop&&(e.scrollTop=0),e.scrollLeft&&(e.scrollLeft=0)},{passive:!0}),e.scrollTop=0,e.scrollLeft=0)}function tt(){const n=[window];try{window.parent&&window.parent!==window&&n.push(window.parent)}catch{}return n}function os(n){const e=n.wp?.os?.windowManager?.getAll?.()??[];for(const t of e){const s=t.iframe??t.element?.querySelector?.("iframe");if(s&&s.contentWindow===window)return t}return null}const ls=4e3,cs=100;function ds(n,e){const t=Date.now()+ls,s=()=>{const a=os(n);if(a&&a.id!==e){typeof a.close=="function"?a.close():n.wp?.os?.windowManager?.remove?.(a.id);return}!a&&Date.now()0?{form:t}:void 0;t>0&&fs(t);for(const a of tt())if(a.wp?.os?.openWindow?.(e,{source:"handoff",params:s})){window.setTimeout(()=>ds(a,e),0);return}}function us(){document.addEventListener("click",n=>{const e=n.target?.closest?.("[data-atf-open-window]");if(!e)return;n.preventDefault();const t=e.getAttribute("data-atf-open-window")??"";for(const s of tt())if(s.wp?.os?.openWindow?.(t,{source:"handoff"}))return})}const ps="allterrain-forms/requested-form";function fs(n){try{window.sessionStorage.setItem(ps,String(n))}catch{}}function st(n){return`allterrain-forms/requested-form-${n}`}function ms(n){try{const e=window.sessionStorage.getItem(st(n));return window.sessionStorage.removeItem(st(n)),Number(e)||0}catch{return 0}}const Te={is:"is",is_not:"is not",contains:"contains",not_contains:"does not contain",starts_with:"starts with",ends_with:"ends with",greater:"is more than",less:"is less than",greater_equal:"is at least",less_equal:"is at most",empty:"is empty",not_empty:"has any answer"},Me=["empty","not_empty"];function nt(n,e){const t=n.find(s=>s.id===e);return t?t.label||"an untitled question":null}function at(n,e,t){return n.find(i=>i.id===e)?.choices?.find(i=>i.value===t)?.label||t}function bs(n,e){const t=Te[n.operator]??String(n.operator);if(Me.includes(n.operator))return t;const s=at(e,n.field,n.value);return`${n.operator==="is"?"":`${t} `}${s!==""?s:"(nothing)"}`}function it(n,e,t=0){const s=nt(e,n.field),a=Te[n.operator]??String(n.operator);if(s===null)return[{kind:"field",text:"a question that no longer exists",fieldId:n.field,missing:!0}];const i=[{kind:"field",text:s,fieldId:n.field,missing:!1},{kind:"operator",text:a,operator:n.operator,ruleIndex:t}];if(Me.includes(n.operator))return i;const o=at(e,n.field,n.value);return i.push({kind:"value",text:o!==""?o:"(nothing)",raw:n.value,sourceId:n.field,ruleIndex:t}),i}function gs(n,e){const t=n.logic;if(!t?.enabled||!t.rules.length)return[];const s=[{kind:"verb",text:t.action==="hide"?"Hidden when":"Shown when"}];return t.rules.forEach((a,i)=>{i>0&&s.push({kind:"join",text:t.match==="all"?"and":"or"}),s.push(...it(a,e,i))}),s}function rt(n){return n.map(e=>e.text).join(" ")}function vs(n,e){return rt(it(n,e))}function me(n){const e=[];for(const t of n){const s=t.logic;if(s?.enabled)for(const a of s.rules)!a.field||a.field===t.id||e.push({from:a.field,to:t.id,label:vs(a,n),short:bs(a,n),action:s.action,broken:nt(n,a.field)===null})}return e}function ys(n){const e=new Map,t=new Set;for(const s of me(n)){const a=`${s.from}->${s.to}`;s.broken||t.has(a)||(t.add(a),e.set(s.from,(e.get(s.from)??0)+1))}return e}const ot=10,lt=22,ne="http://www.w3.org/2000/svg";class ws{constructor(e){this.edges=[],this.teardowns=[],this.frame=0,this.host=e,this.svg=document.createElementNS(ne,"svg"),this.svg.setAttribute("class","atfb-logicmap"),this.svg.setAttribute("aria-hidden","true"),this.svg.setAttribute("focusable","false"),e.append(this.svg);const t=()=>this.schedule();window.addEventListener("resize",t),this.teardowns.push(()=>window.removeEventListener("resize",t));const s=e.closest(".atfb__canvas")??e;if(s.addEventListener("scroll",t,{passive:!0}),this.teardowns.push(()=>s.removeEventListener("scroll",t)),typeof ResizeObserver<"u"){const a=new ResizeObserver(t);a.observe(e),this.teardowns.push(()=>a.disconnect())}}setEdges(e){this.edges=e,this.schedule()}highlight(e){this.svg.classList.toggle("is-focused",!!e),this.svg.querySelectorAll("[data-from]").forEach(t=>{const s=e&&(t.dataset.from===e||t.dataset.to===e);t.classList.toggle("is-lit",!!s)})}destroy(){this.teardowns.forEach(e=>e()),this.teardowns=[],this.svg.remove(),this.frame&&cancelAnimationFrame(this.frame)}schedule(){this.frame||(this.frame=requestAnimationFrame(()=>{this.frame=0,this.draw()}))}draw(){this.svg.replaceChildren();const e=this.host.getBoundingClientRect();this.svg.setAttribute("viewBox",`0 0 ${e.width} ${e.height}`),this.svg.setAttribute("width",String(e.width)),this.svg.setAttribute("height",String(e.height));const t=new Map;for(const s of this.edges){const a=this.cardRect(s.from),i=this.cardRect(s.to);if(!a||!i)continue;const o=t.get(s.from)??0;t.set(s.from,o+1),this.drawEdge(s,a,i,e,o)}}cardRect(e){const t=this.host.querySelector(`[data-atfb-card="${CSS.escape(e)}"]`);return t?t.getBoundingClientRect():null}drawEdge(e,t,s,a,i){const o=t.right-a.left,l=t.top+t.height/2-a.top,c=s.right-a.left,d=s.top+s.height/2-a.top,h=Math.max(0,a.width-ot-Math.max(o,c)),w=22+i*11+Math.min(22,Math.abs(d-l)/8),_=Math.max(12,Math.min(w,h*.4)),u=document.createElementNS(ne,"path");u.setAttribute("d",`M ${o} ${l} C ${o+_} ${l}, ${c+_} ${d}, ${c} ${d}`),u.setAttribute("class",`atfb-logicmap__path is-${e.action}${e.broken?" is-broken":""}`),u.dataset.from=e.from,u.dataset.to=e.to;const b=document.createElementNS(ne,"title");b.textContent=e.label,u.append(b);const g=document.createElementNS(ne,"circle");g.setAttribute("cx",String(c)),g.setAttribute("cy",String(d)),g.setAttribute("r","3.5"),g.setAttribute("class",`atfb-logicmap__dot is-${e.action}${e.broken?" is-broken":""}`),g.dataset.from=e.from,g.dataset.to=e.to,this.svg.append(u,g);const p=document.createElementNS(ne,"text");p.setAttribute("x",String(a.width-ot)),p.setAttribute("y",String(d+3)),p.setAttribute("text-anchor","end"),p.setAttribute("class","atfb-logicmap__label"),p.dataset.from=e.from,p.dataset.to=e.to,p.textContent=e.short.length>lt?`${e.short.slice(0,lt-1)}…`:e.short;const x=document.createElementNS(ne,"title");x.textContent=e.label,p.append(x),this.svg.append(p)}}const _s={text:"text",email:"text",url:"text",tel:"text",number:"text",password:"text",date:"text",time:"text",datetime:"text",date_range:"composite",textarea:"textarea",select:"select",country:"select",multiselect:"options",radio:"options",checkboxes:"options",image_choice:"options",quiz:"options",switch:"toggle",consent:"toggle",file:"file",range:"range",rating:"summary",scale:"summary",likert:"summary",signature:"summary",repeater:"repeater",color:"text",name:"composite",address:"composite",total:"text",hidden:"static",heading:"static",html:"static",divider:"static",spacer:"static",page_break:"static"};function xs(n){return _s[n]??"text"}function ct(n,e){const t=/^choice:(\d+):(label|value)$/.exec(e);return String(t?n.choices?.[Number(t[1])]?.[t[2]]??"":n[e]??"")}function V(n){const{value:e,onInput:t,onCommit:s}=n,a=r("span",{class:`${n.class} atfb-editable`,text:e,attrs:{contenteditable:"plaintext-only",role:"textbox",spellcheck:"false","data-placeholder":n.placeholder}});return n.bind&&(a.dataset.atfbBind=n.bind),a.addEventListener("pointerdown",i=>i.stopPropagation()),a.addEventListener("input",()=>t(a.textContent??"")),a.addEventListener("keydown",i=>{i.key==="Enter"&&(i.preventDefault(),a.blur()),i.key==="Escape"&&(a.textContent=e,a.blur()),i.stopPropagation()}),a.addEventListener("blur",()=>s?.()),a}function ks(n){const e=r("input",{class:"atf-choice__input",type:n==="checkboxes"||n==="multiselect"?"checkbox":"radio"});return e.disabled=!0,e}function dt(n,e,t){const s=xs(n.type),a=V({value:n.label,placeholder:"Write the question…",class:"atf-label",bind:"label",onInput:o=>t.edit(l=>{l.label=o}),onCommit:()=>t.restructure(()=>{})}),i=[s==="static"||s==="toggle"?null:a,Ls(n,e,s,t),Ss(n,e,t)];return r("div",{class:"atfb-preview atf-form",children:[r("div",{class:`atf-field atf-field--${n.type}`,children:i})]})}function Ss(n,e,t){if(!e?.supports.includes("hint"))return null;const s=V({value:n.hint??"",placeholder:"Add a hint…",class:"atf-hint",bind:"hint",onInput:a=>t.edit(i=>{i.hint=a})});return s.setAttribute("aria-label","Hint"),r("p",{class:"atfb-preview__hint",children:[s]})}function Ls(n,e,t,s){switch(t){case"text":return Fe(n,"atf-input",s);case"textarea":return Fe(n,"atf-input atf-textarea",s,!0);case"select":return r("div",{class:"atfb-preview__select",children:[Fe(n,"atf-input atf-select",s)]});case"options":return As(n,s);case"toggle":return r("div",{class:n.type==="switch"?"atf-toggle atf-toggle--switch":"atf-toggle",children:[(()=>{const a=r("input",{class:"atf-toggle__input",type:"checkbox"});return a.disabled=!0,a})(),V({value:n.label||"",placeholder:n.type==="consent"?"What are they agreeing to?…":"What does this turn on?…",class:"atf-toggle__label",bind:"label",onInput:a=>s.edit(i=>{i.label=a}),onCommit:()=>s.restructure(()=>{})})]});case"file":return r("div",{class:"atf-file",children:[r("input",{class:"atf-file__input",type:"file"})]});case"range":return r("input",{class:"atf-range__input",type:"range"});case"composite":return r("div",{class:"atf-composite__parts",children:[r("span",{class:"atf-input atfb-preview__ghost",text:""}),r("span",{class:"atf-input atfb-preview__ghost",text:""})]});case"static":return Ts(n,e,s);case"repeater":return Es(n,s);default:return r("div",{class:"atfb-preview__stack",children:[r("p",{class:"atfb-preview__summary",text:`${e?.label??n.type} — set this up in the panel on the right.`})]})}}function Es(n,e){const t=n.fields??[],s=r("div",{class:"atfb-repeater__zone",attrs:{"data-atfb-repeater-zone":n.id}});return t.forEach(a=>s.append(Cs(n,a,e))),t.length||s.append(r("p",{class:"atfb-repeater__empty",text:"Drag fields from the palette in here — the visitor gets a fresh copy of each with every row they add."})),r("div",{class:"atfb-repeater",attrs:{"data-atfb-repeater":n.id},children:[r("div",{class:"atf-repeater__row atfb-repeater__frame",children:[r("div",{class:"atf-repeater__row-head",children:[r("span",{class:"atf-repeater__title atfb-repeater__title",children:[V({value:String(n.itemLabel??""),placeholder:"Row",class:"atfb-repeater__item-label",bind:"itemLabel",onInput:a=>e.edit(i=>{i.itemLabel=a})}),r("span",{class:"atfb-repeater__ordinal",text:"1",attrs:{"aria-hidden":"true"}})]})]}),s]}),$s(n,e)]})}function Cs(n,e,t){const s=e.id,a=l=>c=>{const d=(c.fields??[]).find(h=>h.id===s);d&&l(d)},i={edit:l=>t.edit(a(l)),restructure:l=>t.restructure(a(l)),types:t.types,selectedId:t.selectedId},o=t.types?.(e.type);return r("div",{class:`atfb-subcard${t.selectedId===s?" is-selected":""}`,attrs:{"data-atfb-subfield":s,"data-atfb-parent":n.id,tabindex:"0",role:"button","aria-label":`${e.label||o?.label||e.type}, inside ${n.label||"the repeater"}`},children:[r("div",{class:"atfb-subcard__head",children:[r("span",{class:"atfb-subcard__type",text:o?.label??e.type}),e.required?r("span",{class:"atfb-subcard__required",text:"Required"}):null,r("button",{class:"atfb-preview__remove",type:"button",title:"Remove this field from the repeater",attrs:{"aria-label":`Remove ${e.label||o?.label||"this field"}`},on:{pointerdown:l=>l.stopPropagation(),click:l=>{l.stopPropagation(),t.restructure(c=>{const d=c.fields??[],h=d.findIndex(w=>w.id===s);h>=0&&d.splice(h,1)})}},children:[r("span",{text:"×"})]})]}),dt(e,o,i)]})}function Fe(n,e,t,s=!1){const a=V({value:n.placeholder??"",placeholder:n.type==="select"||n.type==="country"?"Choose…":"Placeholder…",class:`${e} atfb-preview__box${s?" atfb-preview__box--tall":""}`,bind:"placeholder",onInput:i=>t.edit(o=>{o.placeholder=i})});return a.setAttribute("aria-label","Placeholder"),a}function Ie(n,e,t,s,a,i){return r("span",{class:`atf-button ${t} atfb-preview__button`,children:[V({value:n,placeholder:e,class:"atfb-preview__button-text",bind:s,onInput:o=>a.edit(l=>i(l,o))})]})}function $s(n,e){return Ie(String(n.addLabel??""),"Add another","atf-button--ghost atfb-repeater__add","addLabel",e,(t,s)=>{t.addLabel=s})}function As(n,e){const t=r("div",{class:"atf-choices__list"});return(n.choices??[]).forEach((s,a)=>{t.append(r("div",{class:"atf-choice atfb-preview__option",children:[ks(n.type),V({value:s.label,placeholder:"Option…",class:"atf-choice__label",bind:`choice:${a}:label`,onInput:i=>{e.edit(o=>{const l=o.choices?.[a];if(!l)return;const c=!l.value||l.value===l.label;l.label=i,c&&(l.value=i)})}}),r("button",{class:"atfb-preview__remove",type:"button",title:"Remove this option",attrs:{"aria-label":`Remove ${s.label||"this option"}`},on:{pointerdown:i=>i.stopPropagation(),click:i=>{i.stopPropagation(),e.restructure(o=>{o.choices.splice(a,1)})}},children:[r("span",{text:"×"})]})]}))}),t.append(r("button",{class:"atfb-preview__add",type:"button",on:{pointerdown:s=>s.stopPropagation(),click:s=>{s.stopPropagation(),e.restructure(a=>{const i=a.choices.length+1;a.choices.push({label:`Option ${i}`,value:`Option ${i}`})})}},children:[r("span",{text:"+ Add option"})]})),r("fieldset",{class:"atf-choices",children:[t]})}function Ts(n,e,t){return n.type==="heading"?V({value:n.label,placeholder:"Section heading…",class:"atf-heading",bind:"label",onInput:s=>t.edit(a=>{a.label=s}),onCommit:()=>t.restructure(()=>{})}):n.type==="divider"?r("hr",{class:"atf-divider"}):n.type==="page_break"?r("div",{class:"atfb-preview__stack",children:[r("p",{class:"atfb-progress-name",children:[V({value:n.label,placeholder:"Name this step…",class:"atf-progress__label",bind:"label",onInput:s=>t.edit(a=>{a.label=s}),onCommit:()=>t.restructure(()=>{})})]}),r("p",{class:"atfb-preview__summary",text:"Everything after this is a new page."}),r("div",{class:"atf-nav atfb-preview__nav",children:[Ie(String(n.prevLabel??""),"Back","atf-button--secondary","prevLabel",t,(s,a)=>{s.prevLabel=a}),Ie(String(n.nextLabel??""),"Next","","nextLabel",t,(s,a)=>{s.nextLabel=a})]})]}):r("p",{class:"atfb-preview__summary",text:`${e?.label??n.type} — nothing is shown to the visitor here.`})}const De=new Map;function qe(n){let e=De.get(n);return e||(e=F.mergeTags(n).catch(()=>[]),De.set(n,e)),e}function Ms(n){De.delete(n)}function Fs(n){const e=new Map;for(const t of n)for(const s of t.items)e.set(s.tag,s);return e}function Is(n,e){const t=Fs(e);return n.replace(/\{[a-z_]+(?::[^}]*)?\}/gi,s=>{const a=t.get(s.toLowerCase());return a?a.sample:s})}function Ds(n){return/\{[a-z_]+(?::[^}]*)?\}/i.test(n)}let te=null;function re(){te?.remove(),te=null}typeof document<"u"&&(document.addEventListener("pointerdown",n=>{const e=n.target;e?.closest(".atfb-tagpick__open")||te&&!te.contains(e)&&re()}),document.addEventListener("keydown",n=>{n.key==="Escape"&&te&&(re(),n.stopPropagation())}));function ht(n,e){const t=n.selectionStart??n.value.length,s=n.selectionEnd??n.value.length;n.value=n.value.slice(0,t)+e+n.value.slice(s);const a=t+e.length;n.setSelectionRange(a,a),n.focus(),n.dispatchEvent(new Event("input",{bubbles:!0}))}function qs(n){let e=n.parentElement;for(;e&&e!==document.body;){const t=getComputedStyle(e).overflowY;if(t==="auto"||t==="scroll"||t==="hidden")return e.getBoundingClientRect().bottom;e=e.parentElement}return window.innerHeight}function Os(n,e){const t=r("input",{class:"atfb-input atfb-tagpick__search",type:"search",placeholder:"Search values…",attrs:{"aria-label":"Search values"}}),s=r("div",{class:"atfb-tagpick__list"}),a=i=>{s.replaceChildren();const o=i.trim().toLowerCase();let l=0;for(const c of n){const d=c.items.filter(h=>!o||h.label.toLowerCase().includes(o)||h.tag.toLowerCase().includes(o));if(!d.length){c.empty&&!o&&!c.items.length&&s.append(r("p",{class:"atfb-tagpick__group",text:c.label}),r("p",{class:"atfb-tagpick__empty",text:c.empty}));continue}s.append(r("p",{class:"atfb-tagpick__group",text:c.label}));for(const h of d)l+=1,s.append(r("button",{class:"atfb-tagpick__item",type:"button",on:{click:()=>{e(h.tag),re()}},children:[r("span",{class:"atfb-tagpick__item-main",children:[r("span",{class:"atfb-tagpick__label",text:h.label}),r("code",{class:"atfb-tagpick__tag",text:h.tag})]}),h.hint||h.sample?r("span",{class:"atfb-tagpick__meta",text:h.sample?`e.g. ${h.sample}`:h.hint}):null]}))}!l&&o&&s.append(r("p",{class:"atfb-tagpick__empty",text:`Nothing matches “${i}”.`}))};return a(""),t.addEventListener("input",()=>a(t.value)),r("div",{class:"atfb-tagpick",attrs:{role:"dialog","aria-label":"Insert a value"},children:[r("p",{class:"atfb-tagpick__intro",text:"Pick something to drop in. It is filled in when the form is submitted."}),t,s]})}function ut(n,e){const t=r("button",{class:"atfb-button atfb-button--ghost atfb-tagpick__open",type:"button",title:"Insert a value from the submission",children:[z("shortcode"),r("span",{text:"Insert a value"})]}),s=r("div",{class:"atfb-taggable",children:[n,r("div",{class:"atfb-taggable__tools",children:[t]})]}),a=e.preview===!1?null:r("p",{class:"atfb-taggable__preview"});a&&s.append(a);const i=()=>{if(a){if(!Ds(n.value)){a.textContent="",a.hidden=!0;return}qe(e.formId).then(o=>{a.hidden=!1,a.replaceChildren(r("span",{class:"atfb-taggable__preview-label",text:"Reads as"}),r("span",{text:Is(n.value,o)}))})}};return n.addEventListener("input",i),i(),t.addEventListener("click",o=>{if(o.stopPropagation(),te&&s.contains(te)){re();return}re(),qe(e.formId).then(l=>{const c=Os(l,d=>{ht(n,d),i()});s.append(c),te=c,(window.innerHeight-c.getBoundingClientRect().topqs(s))&&c.classList.add("atfb-tagpick--above"),c.querySelector(".atfb-tagpick__search")?.focus()})}),s}function pt(n,e){const t=parseFloat(String(n??""));return Number.isFinite(t)?t:e}function ft(n,e){let t=e[0];for(const s of e)Math.abs(s.at-n)n.accent??"#2271b1",apply:n=>({accent:n,"accent-soft":`color-mix( in srgb, ${n} 12%, transparent )`,"border-focus":n,"focus-ring-color":n,"button-bg":n,"button-bg-hover":`color-mix( in srgb, ${n} 88%, #000 )`})},{id:"roundness",label:"Roundness",hint:"Corners, everywhere at once.",kind:"scale",steps:Oe.map(({value:n,label:e})=>({value:n,label:e})),owns:["radius-field","radius-button","radius-card","radius-check"],read:n=>ft(pt(n["radius-field"],4),Oe),apply:n=>{const e=Oe.find(t=>t.value===n)?.at??4;return{"radius-field":`${e}px`,"radius-button":`${e}px`,"radius-card":`${Math.min(e*2,28)}px`,"radius-check":`${Math.min(e,6)}px`}}},{id:"density",label:"Density",hint:"How much air the form has.",kind:"scale",steps:Ne.map(({value:n,label:e})=>({value:n,label:e})),owns:["pad-field-x","pad-field-y","gap-fields","gap-label","button-pad-x","button-pad-y"],read:n=>ft(pt(n["pad-field-y"],9),Ne),apply:n=>{const e=Ne.find(t=>t.value===n)?.at??9;return{"pad-field-y":`${e}px`,"pad-field-x":`${Math.round(e*1.4)}px`,"gap-fields":`${Math.round(e*2.2)}px`,"gap-label":`${Math.max(4,Math.round(e*.7))}px`,"button-pad-y":`${e+2}px`,"button-pad-x":`${Math.round(e*2.2)}px`}}},{id:"shadow",label:"Depth",hint:"How far the form sits off the page.",kind:"scale",steps:Ns,owns:["shadow-field","shadow-field-focus","shadow-button","shadow-button-hover","shadow-card"],read:n=>{const e=n["shadow-card"]??"none";return e==="none"||e.trim()===""?n["shadow-field"]&&n["shadow-field"]!=="none"?"hard":"none":e.includes("0 1px")||e.includes("2px")?"subtle":"lifted"},apply:n=>{switch(n){case"subtle":return{"shadow-field":"none","shadow-field-focus":"none","shadow-button":"0 1px 2px rgba( 0, 0, 0, 0.12 )","shadow-button-hover":"0 2px 4px rgba( 0, 0, 0, 0.16 )","shadow-card":"0 1px 3px rgba( 0, 0, 0, 0.1 )"};case"lifted":return{"shadow-field":"none","shadow-field-focus":"0 4px 12px rgba( 0, 0, 0, 0.12 )","shadow-button":"0 4px 10px rgba( 0, 0, 0, 0.16 )","shadow-button-hover":"0 8px 20px rgba( 0, 0, 0, 0.2 )","shadow-card":"0 10px 30px rgba( 0, 0, 0, 0.14 )"};case"hard":return{"shadow-field":"3px 3px 0 currentColor","shadow-field-focus":"5px 5px 0 currentColor","shadow-button":"4px 4px 0 currentColor","shadow-button-hover":"2px 2px 0 currentColor","shadow-card":"none"};default:return{"shadow-field":"none","shadow-field-focus":"none","shadow-button":"none","shadow-button-hover":"none","shadow-card":"none"}}}},{id:"field-style",label:"Fields",hint:"How an input is drawn.",kind:"choice",steps:Rs,owns:["field-style"],read:n=>n["field-style"]??"outline",apply:n=>({"field-style":n})},{id:"labels",label:"Labels",hint:"Where the question sits relative to the answer.",kind:"choice",steps:Ps,owns:["label-position"],read:n=>n["label-position"]??"top",apply:n=>({"label-position":n})}]}function Bs(n){return Re().find(e=>e.owns.includes(n))??null}function mt(n){let e=n.activeSlug,t={...n.overrides},s=n.themes;const a=m=>{t=m,n.onOverridesReplaced?.({...m})},i=r("div",{class:"atfs-preview__frame"}),o=r("div",{class:"atfs-controls__body"}),l=r("div",{class:"atfs-quick"}),c=r("div",{class:"atfs-themes"}),d=$e(async()=>{try{const m=await n.previewFor(e,t),v=i.closest(".atf-studio__preview"),k=v?.scrollTop??0;i.innerHTML=m,v&&(v.scrollTop=k),document.dispatchEvent(new CustomEvent("alltfo-refresh"))}catch(m){R(i),i.append(r("p",{class:"atfb-error",text:m instanceof Error?m.message:"Preview failed."}))}},180),h={"label-position":"atf-labels-","field-style":"atf-fields-"},w=(m,v,k)=>{const L=k.replace(/[^a-z0-9_-]/gi,"");for(const A of[...m.classList])A.startsWith(v)&&m.classList.remove(A);L&&m.classList.add(v+L)},_=m=>{const v=i.querySelector(".atf-form-wrap"),k=v?.querySelector(".atf-form");if(v)for(const[L,A]of Object.entries(m))A===""?v.style.removeProperty(`--atf-${L}`):v.style.setProperty(`--atf-${L}`,A),k&&h[L]&&w(k,h[L],A||(s.find(P=>P.slug===e)?.resolved[L]??""))},u=m=>{_(m.resolved);const v=i.querySelector(".atf-form");v&&(w(v,"atf-theme-",m.slug),v.classList.toggle("atf-is-dark",!!m.dark))},b=m=>t[m.token]!==void 0?t[m.token]:s.find(k=>k.slug===e)?.resolved?.[m.token]??m.default,g=()=>{R(c);for(const m of s){const v=r("button",{class:`atfs-theme${m.slug===e?" is-active":""}`,type:"button",attrs:{"aria-pressed":m.slug===e,title:m.description},children:[r("span",{class:"atfs-theme__chip",style:{background:m.resolved.surface??"#fff",borderColor:m.resolved.border??"#ccc",borderRadius:m.resolved["radius-field"]??"4px",boxShadow:m.resolved["shadow-card"]??"none"},children:[r("span",{class:"atfs-theme__accent",style:{background:m.resolved.accent??"#2271b1",borderRadius:m.resolved["radius-button"]??"4px"}})]}),r("span",{class:"atfs-theme__name",text:m.label}),m.custom?r("span",{class:"atfb-badge",text:"yours"}):null],on:{click:()=>{e=m.slug,a({}),n.onTheme(e),u(m),g(),S(),I(),Yt()}}});c.append(v)}},p=()=>{const v={...s.find(k=>k.slug===e)?.resolved??{}};for(const[k,L]of Object.entries(t))v[k]=L;return v},x=m=>{const v=l.closest(".atf-studio__controls"),k=v?.scrollTop??0;m(),v&&(v.scrollTop=k)},I=$e(()=>x(f),150),S=()=>{const m=p(),v=l.querySelectorAll(".atfs-dial");Re().forEach((k,L)=>{const A=v[L];if(!A)return;const P=k.read(m);if(k.kind==="colour"){const q=A.querySelector('input[type="color"]'),X=A.querySelector("input.atfb-input");q&&document.activeElement!==q&&(q.value=be(P)),X&&document.activeElement!==X&&(X.value=P);return}A.querySelectorAll(".atfs-segment").forEach((q,X)=>{const Q=k.steps?.[X]?.value===P;q.classList.toggle("is-on",Q),q.setAttribute("aria-pressed",String(Q))})})},H=(m,v)=>{const k=m.apply(v,p());for(const[L,A]of Object.entries(k))t[L]=A,n.onOverride(L,A);_(k),S(),I()},E=()=>{R(l);const m=p();for(const v of Re()){const k=v.read(m);let L;if(v.kind==="colour"){const A=r("input",{class:"atfs-color",type:"color",value:be(k),attrs:{"aria-label":v.label},on:{input:P=>H(v,P.target.value)}});L=r("div",{class:"atfs-color-row",children:[A,T(k,P=>H(v,P))]})}else L=r("div",{class:`atfs-segmented atfs-segmented--${v.kind}`,attrs:{role:"group","aria-label":v.label},children:(v.steps??[]).map(A=>r("button",{class:`atfs-segment${A.value===k?" is-on":""}`,type:"button",text:A.label,attrs:{"aria-pressed":A.value===k},on:{click:()=>H(v,A.value)}}))});l.append(r("div",{class:"atfs-dial",children:[r("div",{class:"atfs-dial__head",children:[r("span",{class:"atfs-dial__label",text:v.label}),r("span",{class:"atfs-dial__hint",text:v.hint})]}),L]}))}},f=()=>{R(o);const m=new Map;for(const k of n.tokens){const L=m.get(k.group)??[];L.push(k),m.set(k.group,L)}const v={colour:"Colour",shape:"Corners and borders",shadow:"Shadows",fields:"Field style",space:"Spacing",type:"Type",labels:"Labels",button:"Buttons",focus:"Focus ring",motion:"Motion"};for(const[k,L]of m)o.append(r("details",{class:"atfs-group",attrs:{open:k==="colour"},children:[r("summary",{text:v[k]??k}),...L.map(A=>M(A))]}))},M=m=>{const v=b(m),k=q=>{q===""?delete t[m.token]:t[m.token]=q,n.onOverride(m.token,q),_({[m.token]:q})};let L;if(m.control==="color"){const q=r("input",{class:"atfs-color",type:"color",value:be(v),attrs:{"aria-label":`${m.label} colour`},on:{input:Q=>{const ce=Q.target.value;X.value=ce,k(ce)}}}),X=T(v,Q=>{q.value=be(Q),k(Q)});L=r("div",{class:"atfs-color-row",children:[q,X]})}else if(m.control==="select")L=D(v,(m.options??[]).map(q=>({value:q,label:q})),k);else if(m.control==="length"){const q=parseFloat(v)||0,X=m.unit??"px",Q=m.token.includes("radius")?60:80,ce=r("input",{class:"atfs-range",type:"range",value:String(q),attrs:{min:"0",max:String(Q),step:"1","aria-label":m.label},on:{input:ke=>{const Xt=`${ke.target.value}${X}`;Gt.value=Xt,k(Xt)}}}),Gt=T(v,ke=>{ce.value=String(parseFloat(ke)||0),k(ke)});L=r("div",{class:"atfs-length-row",children:[ce,Gt]})}else L=T(v,k);const A=y(m.label,L),P=Bs(m.token);return P&&(A.classList.add("is-dialled"),A.append(r("span",{class:"atfs-owned",text:P.label}))),t[m.token]!==void 0&&(A.classList.add("is-overridden"),A.append(r("button",{class:"atfs-reset",type:"button",text:"Reset",attrs:{"aria-label":`Reset ${m.label} to the theme's value`},on:{click:()=>{k(""),x(f)}}}))),A},C=async()=>{const m=s.find(L=>L.slug===e),v=m?`${m.label} (mine)`:"My theme",k=window.prompt("Name this theme",v);if(k)try{const L={};for(const P of n.tokens){const q=b(P);q!==P.default&&(L[P.token]=q)}const A=await F.saveTheme({label:k,tokens:L});s=[...s.filter(P=>P.slug!==A.slug),A],n.onThemesChanged(s),e=A.slug,a({}),n.onTheme(e),g(),S(),x(f),N("Theme saved",A.label)}catch(L){N("Could not save the theme",L instanceof Error?L.message:"","error")}},j=async()=>{const m=s.find(v=>v.slug===e);if(m?.custom&&await ie(`Delete “${m.label}”? Forms using it fall back to Clean.`,"Delete theme"))try{await F.deleteTheme(m.id),s=s.filter(k=>k.slug!==m.slug),n.onThemesChanged(s),e="clean",a({}),n.onTheme(e),g(),S(),x(f);const v=s.find(k=>k.slug===e);v&&u(v)}catch(v){N("Could not delete the theme",v instanceof Error?v.message:"","error")}},B=async()=>{const m=s.find(L=>L.slug===e),v={label:m?.label??e,tokens:{...m?.tokens??{},...t}},k=JSON.stringify(v,null," ");try{await navigator.clipboard.writeText(k),N("Theme copied","Paste it into another site to import it.")}catch{window.prompt("Copy this theme",k)}},le=()=>{const m=window.prompt("Paste a theme");if(m)try{const v=JSON.parse(m);if(!v.tokens||typeof v.tokens!="object")throw new Error("That does not look like a theme.");for(const[k,L]of Object.entries(v.tokens))t[k]=String(L),n.onOverride(k,String(L));_({...t}),S(),x(f)}catch(v){N("Could not read that theme",v instanceof Error?v.message:"","error")}},zt=$("Delete",()=>void j(),"danger"),Yt=()=>{const m=s.find(v=>v.slug===e);zt.hidden=!m?.custom};return g(),E(),f(),d(),Yt(),r("div",{class:"atf-studio",children:[r("div",{class:"atf-studio__top",children:[r("div",{class:"atf-studio__topbar",children:[r("h2",{class:"atf-studio__heading",text:"Theme"}),r("div",{class:"atfs__actions",children:[$("Save as a theme",()=>void C(),"primary"),$("Export",()=>void B()),$("Import",le),zt]})]}),c]}),r("div",{class:"atf-studio__panes",children:[r("aside",{class:"atf-studio__controls",children:[r("p",{class:"atfb-hint",text:"Changes apply to this form only, until you save them as a theme."}),l,r("details",{class:"atfs-advanced",children:[r("summary",{text:"Every setting"}),r("p",{class:"atfb-hint",text:"The full token list. The dials above write into it."}),o]})]}),r("main",{class:"atf-studio__preview",children:[r("h2",{class:"screen-reader-text",text:"Live preview"}),i]})]})]})}async function Pe(){const n=document.querySelector("[data-atfs-root]");if(!n||n.dataset.atfsMounted)return;n.dataset.atfsMounted="1",Ae(n);const e=n.querySelector("[data-atfs-bar]"),t=n.querySelector(".atfs__body")??n;try{const[s,a,i]=await Promise.all([F.config(),F.listThemes(),F.listForms()]);if(!i.length){R(t),t.append(r("div",{class:"atfb-empty",children:[r("h2",{text:"No forms to preview"}),r("p",{text:"Make a form first — a theme needs something to dress."})]}));return}let o=i[0],l={},c=o.theme;const d=()=>{R(t),t.append(mt({themes:a,tokens:s.tokens,activeSlug:c,overrides:l,standalone:!0,onTheme:h=>{c=h},onOverride:(h,w)=>{w===""?delete l[h]:l[h]=w},onOverridesReplaced:h=>{l={...h}},previewFor:async(h,w)=>{const _=await F.getForm(o.id);_.schema.settings.theme=h,_.schema.settings.themeOverrides=w;const{html:u}=await F.preview(o.id,{schema:_.schema,theme:h});return u},onThemesChanged:h=>{a.length=0,a.push(...h)}}))};e&&(R(e),e.append(r("span",{class:"atfs__label",text:"Preview against"}),D(String(o.id),i.map(h=>({value:String(h.id),label:h.title||"(untitled)"})),h=>{o=i.find(w=>String(w.id)===h)??i[0],l={},d()}))),d()}catch(s){R(t),t.append(r("p",{class:"atfb-error",text:s instanceof Error?s.message:"Could not load themes."}))}}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>void Pe()):Pe(),document.addEventListener("os-window-content-loaded",()=>void Pe()));function be(n){const e=n.trim();return/^#[0-9a-f]{6}$/i.test(e)?e:/^#[0-9a-f]{3}$/i.test(e)?`#${e[1]}${e[1]}${e[2]}${e[2]}${e[3]}${e[3]}`:"#888888"}const Hs="mailpoet",ge="",js=["checkbox","checkboxes","toggle","consent","gdpr"],bt=["email"];function gt(n){return n.schema.actions.find(e=>e.type==="mailpoet")}function Be(n){const e=n?.settings??{};return{lists:Array.isArray(e.lists)?e.lists.map(Number).filter(Boolean):[],email_field:typeof e.email_field=="string"?e.email_field:"",first_name_field:typeof e.first_name_field=="string"?e.first_name_field:"",last_name_field:typeof e.last_name_field=="string"?e.last_name_field:""}}function Ws(n){return n?.logic?.enabled?n.logic.rules[0]?.field??ge:ge}function vt(n,e,t){const s=n.find(a=>e.includes(a.type));return s?s.id:t?n.find(a=>t.test(a.label))?.id??"":""}function Us(n,e,t){const s=r("section",{class:"atfm-form"}),a=l=>r("span",{class:`atfm-form__mark${l?"":" atfm-form__mark--off"}`,children:[r("img",{attrs:{src:e.symbol,alt:"",width:"20",height:"20"}})]}),i=l=>{R(s),s.classList.remove("is-open"),s.append(r("div",{class:"atfm-form__head",children:[a(l.length>0),r("div",{class:"atfm-form__title",children:[r("strong",{text:n.title||`Form ${n.id}`}),l.length?r("span",{class:"atfm-form__status is-on",text:`Subscribing → ${l.join(", ")}`}):r("span",{class:"atfm-form__status",text:"Not connected"})]}),$(l.length?"Edit":"Connect",()=>void o(),"secondary")]}))},o=async()=>{s.classList.add("is-open"),R(s),s.append(r("p",{class:"atfm-hint",text:"Loading the form…"}));let l;try{l=await F.getForm(n.id)}catch{R(s),s.append(r("p",{class:"atfm-hint",text:"Could not load this form."}));return}const c=l.schema.fields.filter(f=>f.type!=="page_break"),d=gt(l),h=Be(d),w=new Set(h.lists.filter(f=>e.lists.some(M=>M.id===f)));let _=Ws(d),u=h.email_field||vt(c,bt,/mail/i),b=h.first_name_field,g=h.last_name_field;c.some(f=>f.id===u)||(u=vt(c,bt,/mail/i));const p=c.filter(f=>js.includes(f.type));!d&&p.length&&(_=p[0].id);const x=(f,M)=>[{value:"",label:M},...f.map(C=>({value:C.id,label:C.label||C.id}))],I=r("div",{class:"atfm-lists"});(()=>{R(I);for(const f of e.lists){const M=r("input",{attrs:{type:"checkbox"}});M.checked=w.has(f.id),M.addEventListener("change",()=>{M.checked?w.add(f.id):w.delete(f.id)}),I.append(r("label",{class:"atfm-list",children:[M,r("span",{text:f.name})]}))}})();const H=async(f=!1)=>{const M=l.schema.actions.filter(C=>C.type!=="mailpoet");if(!f){if(!w.size){N("Pick a list","A subscription needs at least one MailPoet list.","error");return}if(!u){N("Pick the email field","MailPoet needs to know which answer is the address.","error");return}M.push({id:Hs,type:"mailpoet",enabled:!0,logic:_===ge?{enabled:!1,action:"show",match:"all",rules:[]}:{enabled:!0,action:"show",match:"all",rules:[{field:_,operator:"not_empty",value:""}]},settings:{lists:[...w],email_field:u,first_name_field:b,last_name_field:g}})}l.schema.actions=M;try{await F.updateForm(l.id,{schema:l.schema})}catch(C){N("Could not save",C instanceof Error?C.message:"","error");return}N(f?"Disconnected":"Connected to MailPoet",f?`${n.title} no longer subscribes anyone.`:`${n.title} now subscribes opted-in visitors.`),i(f?[]:e.lists.filter(C=>w.has(C.id)).map(C=>C.name))};R(s);const E=f=>r("span",{class:"atfm-form__section",text:f});s.append(r("div",{class:"atfm-form__head",children:[a(!!d),r("div",{class:"atfm-form__title",children:[r("strong",{text:n.title||`Form ${n.id}`})]}),$("Close",()=>i(d?e.lists.filter(f=>Be(d).lists.includes(f.id)).map(f=>f.name):[]),"ghost")]}),r("div",{class:"atfm-form__body",children:[E("Where they land"),y("Lists",I,"MailPoet sends its own confirmation email before anyone is truly on a list."),E("Who they are"),y("Email address",D(u,x(c,"— pick a field —"),f=>{u=f}),"The answer that carries their address."),y("First name",D(b,x(c,"— none —"),f=>{b=f})),y("Last name",D(g,x(c,"— none —"),f=>{g=f})),E("When to subscribe"),y("Subscribe",D(_,[...p.map(f=>({value:f.id,label:`Only when “${f.label||f.id}” is ticked`})),{value:ge,label:"Everyone who submits"}],f=>{_=f}),p.length?"Bind it to a box they tick. Subscribing everyone is rarely what visitors expect.":"Add a checkbox or toggle field to the form to offer a proper opt-in."),r("div",{class:"atfm-form__actions",children:d?[$("Save connection",()=>void H(),"primary"),$("Disconnect",()=>void H(!0),"danger")]:[$("Save connection",()=>void H(),"primary")]})]}))};return i([]),F.getForm(n.id).then(l=>{const c=gt(l);if(c&&!s.classList.contains("is-open")){const d=e.lists.filter(h=>Be(c).lists.includes(h.id)).map(h=>h.name);i(d)}}).catch(()=>{}),t.append(s),s}function zs(n){const e=n.logo?r("span",{class:"atfm-hero__logo",children:[r("img",{attrs:{src:n.logo,alt:"MailPoet",width:"210",height:"105"}})]}):r("span",{class:"atfm-hero__logo atfm-hero__logo--glyph",text:"📬"});return r("header",{class:"atfm-hero",children:[e,r("div",{class:"atfm-hero__words",children:[r("h1",{text:"Turn submissions into subscribers"}),r("p",{text:"Every submission is someone choosing to talk to you. Connect a form to MailPoet and the ones who opt in land on your lists by themselves — named, consented, and confirmed by MailPoet’s own double opt-in."}),n.active?r("p",{class:"atfm-hero__status is-on",text:n.lists.length===1?"Connected — 1 list ready for subscribers":`Connected — ${n.lists.length} lists ready for subscribers`}):r("p",{class:"atfm-hero__status",text:"MailPoet is not installed on this site yet"})]})]})}function Ys(){const n=(t,s,a)=>r("div",{class:"atfm-why__card",children:[r("span",{class:"atfm-why__icon",text:t}),r("strong",{text:s}),r("p",{text:a})]}),e=(t,s)=>r("a",{text:s,attrs:{href:t,target:"_blank",rel:"noreferrer"}});return[r("div",{class:"atfm-why",children:[n("✉️","Beautiful emails, made in WordPress","Design newsletters and welcome emails in a drag-and-drop editor that lives in your own admin — no external account to juggle."),n("🤝","Consent you can stand behind","Double opt-in out of the box: every address your forms send over is confirmed by the visitor before a single campaign reaches it."),n("🚀","Free to grow with","Free up to 500 subscribers, welcome automations, WooCommerce emails and open-rate stats included — a paid addon anywhere else.")]}),r("p",{class:"atfm-links",children:[e("https://www.mailpoet.com/features/","Explore MailPoet’s features ↗"),e("https://kb.mailpoet.com/","Guides & docs ↗"),e("https://www.mailpoet.com/pricing/","Plans & the free tier ↗")]})]}function Gs(){const n=(e,t)=>r("div",{class:"atfm-step",children:[r("span",{class:"atfm-step__n",text:e}),r("span",{text:t})]});return r("div",{class:"atfm-steps",children:[n("1","Pick a form below"),n("2","Choose the lists it feeds"),n("3","Bind it to an opt-in — MailPoet confirms the rest")]})}async function He(){const n=document.querySelector("[data-atfm-root]");if(!n||n.dataset.atfmMounted)return;n.dataset.atfmMounted="1",Ae(n);const e=n.querySelector("[data-atfm-bar]"),t=n.querySelector("[data-atfm-body]")??n;try{const[s,a]=await Promise.all([F.mailpoet(),F.listForms()]);if(e?.remove(),R(t),t.append(zs(s),...Ys()),!s.active){t.append(r("div",{class:"atfm-pitch",children:[r("p",{text:"MailPoet sends newsletters from right inside WordPress — no external account, free to start. Install it and every form here gets a subscribe switch: reservations feed your announcements, signups feed your schedule, enquiries feed your news."}),r("a",{class:"atfm-pitch__cta",text:"Install MailPoet — it’s free",attrs:{href:s.adminUrl,target:"_blank",rel:"noreferrer"}})]}));return}if(!s.lists.length){t.append(r("div",{class:"atfm-pitch",children:[r("p",{text:"MailPoet is here, but it has no list yet. Make one, and your forms will have somewhere to send subscribers."}),r("a",{class:"atfm-pitch__cta",text:"Open MailPoet lists",attrs:{href:s.adminUrl,target:"_blank",rel:"noreferrer"}})]}));return}t.append(Gs());const i=r("section",{class:"atfm-forms",children:[r("div",{class:"atfm-forms__head",children:[r("h2",{text:"Your forms"}),r("p",{class:"atfm-hint",text:"Connected forms wear the MailPoet mark in colour."})]})]});if(t.append(i),!a.length){i.append(r("p",{class:"atfm-hint",text:"No forms yet — make one, then come back to connect it."}));return}for(const o of a)Us(o,s,i)}catch{e&&(e.textContent="Could not reach the site — reload the window to try again.")}}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>void He()):He(),document.addEventListener("os-window-content-loaded",()=>void He());const ve={plain:"",simple:"✓",minimal:"",card:"🎉",check:"",confetti:"🎉",fireworks:"🎆",sparkles:"✨",typewriter:""};function yt(){return{style:"simple",title:"",icon:"",accent:"",intensity:"medium",showButton:!1,buttonLabel:""}}function je(n){const e={...yt(),...n??{}};return e.style in ve||(e.style="simple"),e}function Xs(){return typeof window.matchMedia=="function"&&window.matchMedia("(prefers-reduced-motion: reduce)").matches}function Zs(n,e,t){const s=je(e),a=document.createElement("div");if(a.className=`atf-confirmation atf-success atf-success--${s.style}`,a.setAttribute("role","status"),a.setAttribute("tabindex","-1"),s.style==="plain")return a.className="atf-confirmation",a.innerHTML=n,a;s.accent&&a.style.setProperty("--atf-accent",s.accent);const i=document.createElement("div");i.className="atf-success__inner",a.append(i);const o=s.icon||ve[s.style];if(s.style==="check")i.insertAdjacentHTML("beforeend",'');else if(o){const c=document.createElement("span");c.className="atf-success__icon",c.setAttribute("aria-hidden","true"),c.textContent=o,i.append(c)}if(s.title){const c=document.createElement("h2");c.className="atf-success__title",c.textContent=s.title,i.append(c)}const l=document.createElement("div");if(l.className="atf-success__message",l.innerHTML=n,i.append(l),s.style==="typewriter"&&a.setAttribute("aria-label",l.textContent??""),s.showButton){const c=document.createElement("button");c.type="button",c.className="atf-button atf-button--ghost atf-success__again",c.textContent=s.buttonLabel||"Fill it in again",c.addEventListener("click",()=>t?t():window.location.reload()),i.append(c)}return a}function Ks(n,e){const t=je(e);if(Xs())return()=>{};switch(t.style){case"confetti":return Vs(n,t);case"fireworks":return Js(t);case"sparkles":return Qs(n,t);case"typewriter":return en(n);default:return()=>{}}}function oe(n){return{low:.5,medium:1,high:1.8}[n.intensity]}function wt(){const n=document.createElement("canvas");n.className="atf-success-canvas",n.setAttribute("aria-hidden","true");const e=Math.min(window.devicePixelRatio||1,2);n.width=Math.floor(window.innerWidth*e),n.height=Math.floor(window.innerHeight*e),document.body.append(n);const t=n.getContext("2d");return t?.scale(e,e),{canvas:n,ctx:t,stop:()=>n.remove()}}const _t=["#f43f5e","#f59e0b","#10b981","#3b82f6","#8b5cf6","#ec4899","#eab308"];function Vs(n,e){const{ctx:t,stop:s}=wt();if(!t)return s(),()=>{};const a=e.accent?[e.accent,..._t]:_t,i=[],o=n.getBoundingClientRect(),l=o.left+o.width/2,c=Math.min(o.top+40,window.innerHeight-20),d=(x,I,S)=>{const H=S?Math.PI*(1.15+.7*Math.random()):0,E=S?9+Math.random()*8:0;return{x,y:I,vx:S?Math.cos(H)*E*(Math.random()<.5?1:-1):(Math.random()-.5)*1.5,vy:S?Math.sin(H)*E:1+Math.random()*2,w:6+Math.random()*5,h:8+Math.random()*7,angle:Math.random()*Math.PI,spin:(Math.random()-.5)*.3,color:a[Math.floor(Math.random()*a.length)],wobble:Math.random()*Math.PI*2}},h=Math.round(90*oe(e));for(let x=0;x{if(_>=w){window.clearInterval(u);return}i.push(d(Math.random()*window.innerWidth,-20,!1)),_++},2e3/w);let b=0;const g=performance.now(),p=x=>{t.clearRect(0,0,window.innerWidth,window.innerHeight);let I=!1;for(const S of i)S.vy+=.16,S.vx*=.99,S.vy*=.985,S.wobble+=.1,S.x+=S.vx+Math.sin(S.wobble)*.8,S.y+=S.vy,S.angle+=S.spin,S.y{window.clearInterval(u),window.cancelAnimationFrame(b),s()}}function Js(n){const{ctx:e,stop:t}=wt();if(!e)return t(),()=>{};const s=[],a=[],i=Math.round(6*oe(n))+2;let o=0;const l=()=>{s.push({x:window.innerWidth*(.15+.7*Math.random()),y:window.innerHeight,vy:-(9+Math.random()*4),targetY:window.innerHeight*(.15+.3*Math.random()),hue:Math.floor(Math.random()*360)}),o++};l();const c=window.setInterval(()=>{if(o>=i){window.clearInterval(c);return}l()},3500/i),d=u=>{const b=Math.round(70*oe(n));for(let g=0;g{e.clearRect(0,0,window.innerWidth,window.innerHeight);for(let g=s.length-1;g>=0;g--){const p=s[g];p.y+=p.vy,p.vy+=.08,e.fillStyle=`hsl(${p.hue} 90% 65%)`,e.fillRect(p.x-1.5,p.y,3,10),(p.y<=p.targetY||p.vy>=-1)&&(d(p),s.splice(g,1))}for(let g=a.length-1;g>=0;g--){const p=a[g];if(p.x+=p.vx,p.y+=p.vy,p.vy+=.045,p.vx*=.985,p.vy*=.985,p.life-=p.decay,p.life<=0){a.splice(g,1);continue}const x=p.life*(.7+.3*Math.random());e.globalAlpha=Math.max(0,x),e.fillStyle=`hsl(${p.hue} 95% ${55+25*p.life}%)`,e.beginPath(),e.arc(p.x,p.y,1.1+1.6*p.life,0,Math.PI*2),e.fill()}e.globalAlpha=1,!(o>=i&&s.length===0&&a.length===0)&&u-w<9e3?h=window.requestAnimationFrame(_):t()};return h=window.requestAnimationFrame(_),()=>{window.clearInterval(c),window.cancelAnimationFrame(h),t()}}function Qs(n,e){const t=e.icon||ve.sparkles,s=Math.round(22*oe(e)),a=[];let i=0;const o=()=>{const c=document.createElement("span");c.className="atf-success__spark",c.setAttribute("aria-hidden","true"),c.textContent=t,c.style.insetInlineStart=`${4+Math.random()*92}%`,c.style.animationDuration=`${2.6+Math.random()*1.8}s`,c.style.animationDelay=`${Math.random()*.3}s`,c.style.fontSize=`${14+Math.random()*14}px`,c.addEventListener("animationend",()=>c.remove()),n.append(c),a.push(c),i++};o();const l=window.setInterval(()=>{if(i>=s){window.clearInterval(l);return}o()},2800/s);return()=>{window.clearInterval(l),a.forEach(c=>c.remove())}}function en(n){const e=n.querySelector(".atf-success__message");if(!e)return()=>{};const t=e.innerHTML,s=e.textContent??"";if(!s)return()=>{};e.textContent="",e.classList.add("is-typing");const a=Math.min(45,3800/s.length);let i=0;const o=window.setInterval(()=>{i++,e.textContent=s.slice(0,i),i>=s.length&&(window.clearInterval(o),e.classList.remove("is-typing"),e.innerHTML=t)},a);return()=>{window.clearInterval(o),e.classList.remove("is-typing"),e.innerHTML=t}}const tn={min:-1,max:-1,sum:-1,avg:-1,round:-1,ceil:1,floor:1,abs:1,sqrt:1,pow:2},xt={"+":{precedence:1,right:!1},"-":{precedence:1,right:!1},"*":{precedence:2,right:!1},"/":{precedence:2,right:!1},"%":{precedence:2,right:!1},"^":{precedence:4,right:!0}};function sn(n,e,t=[]){if(!n||!n.trim()||n.length>2e3)return null;const s=nn(n,e,t),a=ln(s);if(!a)return null;const i=dn(a);if(!i)return null;const o=hn(i);return o===null||!Number.isFinite(o)?null:o}function nn(n,e,t){return n.replace(/\{([a-zA-Z0-9_]+)(?:\.([a-zA-Z0-9_]+))?\}/g,(s,a,i,o)=>an(n,o,s.length,a,i??"",e,t))}function an(n,e,t,s,a,i,o){const l=rn(o,s),c=Object.prototype.hasOwnProperty.call(i,s)?i[s]:null;if(a===""){const u=l?.type==="repeater"?kt(c).length:We(c,l);return St(u)}const h=(l?.fields??[]).find(u=>u.id===a)??null,w=kt(c).map(u=>We(u[a]??"",h));if(!w.length)return"0";const _=w.map(St);return on(n,e,t)?_.join(", "):_.length===1?_[0]:`( ${_.join(" + ")} )`}function rn(n,e){for(const t of n){if(t.id===e)return t;for(const s of t.fields??[])if(s.id===e)return s}return null}function on(n,e,t){const s=n.slice(e+t).replace(/^\s+/,"");if(s===""||s[0]!==")")return!1;const a=n.slice(0,e).replace(/\s+$/,""),i=/([a-zA-Z_][a-zA-Z0-9_]*)\s*\($/.exec(a);return!!i&&["sum","avg","min","max"].includes(i[1].toLowerCase())}function kt(n){if(!Array.isArray(n))return[];const e=[];for(const t of n){if(!t||typeof t!="object"||Array.isArray(t))continue;Object.values(t).some(a=>a!==""&&a!==null&&a!==void 0&&a!==!1&&!(Array.isArray(a)&&!a.length))&&e.push(t)}return e}function St(n){const e=n.toFixed(10).replace(/0+$/,"").replace(/\.$/,"");return e===""||e==="-"?"0":e}function We(n,e){if(typeof n=="boolean")return n?1:0;if(Array.isArray(n))return n.reduce((a,i)=>a+We(i,e),0);if(n==null||n==="")return 0;const t=e?.choices??[];for(const a of t)if(String(a.value)===String(n)){if(typeof a.price=="number")return a.price;if(typeof a.points=="number")return a.points;break}const s=Number(n);return Number.isFinite(s)?s:0}function ln(n){const e=[];let t=0;for(;ti.precedence||l.precedence===i.precedence&&!i.right){e.push(t.pop());continue}break}t.push(a);break}case"(":t.push(a);break;case")":{for(;t.length&&t[t.length-1].type!=="(";)e.push(t.pop());if(!t.length)return null;if(t.pop(),t.length&&t[t.length-1].type==="function"){const i=t.pop();i.arity=s.length?s.pop():1,e.push(i)}break}}for(;t.length;){const a=t.pop();if(a.type==="(")return null;e.push(a)}return e}function hn(n){const e=[];for(const t of n)switch(t.type){case"number":e.push(t.value);break;case"unary":{if(!e.length)return null;e.push(-e.pop());break}case"operator":{if(e.length<2)return null;const s=e.pop(),a=e.pop();switch(t.value){case"+":e.push(a+s);break;case"-":e.push(a-s);break;case"*":e.push(a*s);break;case"/":e.push(s===0?0:a/s);break;case"%":e.push(s===0?0:a%s);break;case"^":e.push(Math.pow(a,s));break;default:return null}break}case"function":{const s=t.arity??1;if(e.lengtht+s,0);case"avg":return e.reduce((t,s)=>t+s,0)/e.length;case"round":{const t=Math.max(-10,Math.min(10,Math.trunc(e[1]??0))),s=Math.pow(10,t);return Math.round(e[0]*s)/s}case"ceil":return Math.ceil(e[0]);case"floor":return Math.floor(e[0]);case"abs":return Math.abs(e[0]);case"sqrt":return e[0]<0?0:Math.sqrt(e[0]);case"pow":return Math.pow(e[0],e[1]??2);default:return null}}const pn=["sum","min","max","avg","round","ceil","floor","abs","sqrt","pow"],Lt=["number","range","scale","rating","total","select","multiselect","radio","checkboxes","switch","quiz"];function Ue(n,e){return n.filter(t=>t.id!==e&&Lt.includes(t.type))}function fn(n){const e=[];for(const t of n){if(t.type!=="repeater")continue;const s=t.label||t.id;e.push({label:`${s} (how many)`,insert:`{${t.id}}`});for(const a of t.fields??[])Lt.includes(a.type)&&e.push({label:`${s} · ${a.label||a.id}`,insert:`{${t.id}.${a.id}}`})}return e}function mn(n,e){const t={};Ue(n,e).forEach((s,a)=>{t[s.id]=a+1});for(const s of n){if(s.type!=="repeater"||s.id===e)continue;const a=s.fields??[],i=o=>Object.fromEntries(a.map((l,c)=>[l.id,c+1+o]));t[s.id]=[i(0),i(1)]}return t}function bn(n){const e=r("div",{class:"atfb-overlay"}),t=()=>{e.remove(),document.removeEventListener("keydown",s)},s=u=>{u.key==="Escape"&&t()},a=r("textarea",{class:"atfb-input atfb-formula__input",attrs:{rows:"3","aria-label":"Formula"}});a.value=String(n.field.formula??"");const i=r("p",{class:"atfb-formula__result",attrs:{"aria-live":"polite"}}),o=mn(n.fields,n.field.id),l=()=>{const u=a.value.trim();if(u===""){i.textContent="Empty. Reference a question below to start.",i.classList.remove("is-error");return}const b=sn(u,o,n.fields);if(b===null){i.textContent="This does not compute yet — check the braces and parentheses.",i.classList.add("is-error");return}const g=Ue(n.fields,n.field.id).map((p,x)=>`${p.label||p.id} = ${x+1}`).concat(n.fields.filter(p=>p.type==="repeater"&&p.id!==n.field.id).map(p=>`${p.label||p.id} = 2 sample rows`)).join(", ");i.textContent=`With sample answers (${g}): ${b}`,i.classList.remove("is-error")};a.addEventListener("input",l);const c=(u,b,g=0)=>r("button",{class:"atfb-formula__chip",type:"button",text:u,on:{click:()=>{if(ht(a,b),g>0){const p=(a.selectionStart??a.value.length)-g;a.setSelectionRange(p,p)}}}}),d=Ue(n.fields,n.field.id),h=fn(n.fields),w=r("div",{class:"atfb-formula__chips",children:d.length||h.length?[...d.map(u=>c(u.label||u.id,`{${u.id}}`)),...h.map(u=>c(u.label,u.insert))]:[r("p",{class:"atfb-hint",text:"No number-shaped questions yet — add a number, scale or priced choice field and it appears here."})]}),_=r("div",{class:"atfb-formula__chips",children:pn.map(u=>c(`${u}()`,`${u}()`,1))});e.append(r("div",{class:"atfb-modal atfb-formula",attrs:{role:"dialog","aria-label":"Formula editor"},children:[r("h2",{text:"Formula"}),a,i,y("Your questions",w,"Click one to reference its answer."),y("Functions",_),r("div",{class:"atfb-modal__actions",children:[$("Cancel",t),$("Save formula",()=>{n.onSave(a.value.trim()),t()},"primary")]})]})),e.addEventListener("click",u=>{u.target===e&&t()}),document.addEventListener("keydown",s),n.root.append(e),l(),a.focus(),a.setSelectionRange(a.value.length,a.value.length)}const gn="allterrain-forms/form";function vn(){return window.wp?.os?.relations??null}function Et(n){const e=n.closest("[data-window-id], .os-window");if(!e)return null;const t=e.getAttribute("data-window-id");if(t)return t;const s=e.id??"";return s?s.replace(/^wp-window-/,""):null}const yn=6e3,Ct=120;function $t(n,e){const t=vn();if(ze.set(n,e),!t?.set)return;const s=i=>{if(ye.get(n)!==a)return;const o=Et(n);if(!o){Date.now()s(i),Ct);return}try{t.set(o,e)}catch(c){At||(At=!0,console.error("[AllTerrain Forms] The shell refused a window identity.",c,e)),ye.delete(n);return}if(!e||t.get?.(o)?.id===e.id||Date.now()>=i){ye.delete(n);return}window.setTimeout(()=>s(i),Ct)},a=Symbol("atf-identity");ye.set(n,a),s(Date.now()+yn)}let At=!1;const ye=new WeakMap,ze=new Map;function wn(){for(const[n,e]of ze){if(!n.isConnected){ze.delete(n);continue}$t(n,e)}}if(typeof document<"u")for(const n of["os-window-content-loaded","os-window-opened"])document.addEventListener(n,()=>wn());function _n(n,e){return{type:gn,id:n.id,label:n.title||"Untitled form",related:[{id:`allterrain-forms/entries-${n.id}`,label:"Entries for this form",url:`${e}admin.php?page=allterrain-forms-entries&form=${n.id}`,group:"allterrain-forms",groupLabel:"Forms",icon:"dashicons-list-view"}]}}const we={letters:{label:"Letters",chars:"A-Za-zÀ-ÖØ-öø-ÿ"},numbers:{label:"Numbers",chars:"0-9"},spaces:{label:"Spaces",chars:" "},punctuation:{label:`Punctuation ( . , ! ? ' " - )`,chars:`.,!?'"()\\-:;`},symbols:{label:"Symbols ( @ # & _ / + )",chars:"@#&_/+*%="}};function xn(){return{mode:"blocks",starts:"",ends:"",contains:"",notContains:"",chars:[],minLen:"",maxLen:"",regex:"",message:"",tests:[]}}function Tt(n){const e=xn();let t;try{t=JSON.parse(n)}catch{return e}if(!t||typeof t!="object")return e;const s=t;e.mode=s.mode==="regex"?"regex":"blocks";for(const a of["starts","ends","contains","notContains","minLen","maxLen","regex","message"])typeof s[a]=="string"&&(e[a]=s[a]);return Array.isArray(s.chars)&&(e.chars=s.chars.filter(a=>typeof a=="string"&&a in we)),Array.isArray(s.tests)&&(e.tests=s.tests.filter(a=>typeof a=="string").slice(0,10)),e}function _e(n){return n.replace(/[.*+?^${}()|[\]\\/]/g,"\\$&")}function xe(n){if(n.mode==="regex")return n.regex.trim();const e=[],t=n.minLen.trim(),s=n.maxLen.trim();(t!==""||s!=="")&&e.push(`(?=.{${t===""?"0":parseInt(t,10)||0},${s===""?"":parseInt(s,10)||""}}$)`),n.contains!==""&&e.push(`(?=.*${_e(n.contains)})`),n.notContains!==""&&e.push(`(?!.*${_e(n.notContains)})`),n.starts!==""&&e.push(`(?=${_e(n.starts)})`),n.ends!==""&&e.push(`(?=.*${_e(n.ends)}$)`);const a=n.chars.map(o=>we[o]?.chars??"").join(""),i=a?`[${a}]*$`:".*$";return!e.length&&!a?"":`^${e.join("")}${i}`}function Mt(n){if(n.mode==="regex")return n.regex.trim()?`Matches the expression ${n.regex.trim()}`:"";const e=[];if(n.starts&&e.push(`starts with “${n.starts}”`),n.ends&&e.push(`ends with “${n.ends}”`),n.contains&&e.push(`contains “${n.contains}”`),n.notContains&&e.push(`never contains “${n.notContains}”`),n.chars.length){const i=n.chars.map(o=>(we[o]?.label??o).split(" (")[0].toLowerCase());e.push(`uses only ${i.join(" and ")}`)}const t=n.minLen.trim(),s=n.maxLen.trim();return t&&s?e.push(`is ${t}–${s} characters long`):t?e.push(`is at least ${t} characters long`):s&&e.push(`is at most ${s} characters long`),e.length?`The answer ${e.length>1?`${e.slice(0,-1).join(", ")}, and ${e[e.length-1]}`:e[0]}.`:""}function kn(n,e){const t=xe(n);if(t==="")return null;try{return new RegExp(t).test(e)}catch{return null}}function Sn(){return window.wp?.os?.windowManager??null}const Ft="allterrain-forms-validation";function Ln(n){const e=Sn(),t=Et(n.root);if(e?.openChild&&t){En(e,t,n);return}Cn(n)}function En(n,e,t){const s=`${Ft}-${t.field.id}`;n.getById?.(s)&&n.remove?.(s);const a={saved:!1},i=()=>{const o=n.getById?.(s);o?.close?o.close():n.remove?.(s)};n.openChild?.(e,{id:s,baseId:Ft,url:`#${s}`,title:`Custom rule — ${t.field.label||"this question"}`,icon:"dashicons-yes-alt",native:!0,width:560,height:700,minWidth:440,minHeight:480,ephemeral:!0,autofocus:".atfb-valwin__pane:not([hidden]) input, .atfb-valwin__pane:not([hidden]) textarea",render:o=>{const l=r("div",{class:"atfa atfb-valwin atfb-valwin--window"}),c=r("div",{class:"atfb-valwin__scroll"});l.append(c),It(c,t,a,i,"window");const d=c.querySelector(".atfb-modal__actions");d&&l.append(d),o.append(l)},onClose:()=>{a.saved||t.onCancel?.()}})}function Cn(n){const e=r("div",{class:"atfb-overlay"}),t={saved:!1},s=()=>{e.remove(),document.removeEventListener("keydown",a),t.saved||n.onCancel?.()},a=o=>{o.key==="Escape"&&s()},i=r("div",{class:"atfb-modal atfb-valwin",attrs:{role:"dialog","aria-label":"Custom validation rule"},children:[r("h2",{text:"Custom rule"})]});It(i,n,t,s,"overlay"),e.append(i),e.addEventListener("click",o=>{o.target===e&&s()}),document.addEventListener("keydown",a),n.root.append(e),e.querySelector(".atfb-valwin__pane:not([hidden]) input, .atfb-valwin__pane:not([hidden]) textarea")?.focus()}function It(n,e,t,s,a){const i=Tt(String(e.field.validationRecipe??""));i.mode==="blocks"&&xe(i)===""&&e.field.pattern&&(i.mode="regex",i.regex=String(e.field.pattern)),i.message||(i.message=String(e.field.messages?.invalid??""));const o=(f,M)=>{const C=r("input",{class:"atfb-input",value:i[f],placeholder:M,attrs:{type:"text"}});return C.addEventListener("input",()=>{i[f]=C.value,E()}),C},l=(f,M)=>{const C=r("input",{class:"atfb-input atfb-valwin__len",value:i[f],attrs:{type:"number",min:"0","aria-label":M}});return C.addEventListener("input",()=>{i[f]=C.value,E()}),C},c=r("div",{class:"atfb-valwin__chars",children:Object.entries(we).map(([f,M])=>O(M.label,i.chars.includes(f),C=>{i.chars=C?[...i.chars,f]:i.chars.filter(j=>j!==f),E()}))}),d=r("div",{class:"atfb-valwin__pane",children:[y("Starts with",o("starts","e.g. AT-")),y("Ends with",o("ends","e.g. -2026")),y("Must contain",o("contains","e.g. @")),y("Must not contain",o("notContains","e.g. spaces? type one")),y("Only these characters",c,"Leave every box unticked to allow anything."),y("Length",r("div",{class:"atfb-valwin__lengths",children:[r("span",{text:"between"}),l("minLen","Minimum length"),r("span",{text:"and"}),l("maxLen","Maximum length"),r("span",{text:"characters"})]}),"Leave a box empty for no limit.")]}),h=r("textarea",{class:"atfb-input atfb-valwin__regex",attrs:{rows:"2","aria-label":"Regular expression",placeholder:"^AT-[0-9]{4}$"}});h.value=i.regex,h.addEventListener("input",()=>{i.regex=h.value,E()});const w=r("div",{class:"atfb-valwin__pane",children:[y("Expression",h,"A regular expression, without slashes. Checked against the whole answer only if you anchor it with ^ and $.")]}),_=[["blocks","Easy blocks"],["regex","Expression (advanced)"]],u=f=>{i.mode=f,d.hidden=f!=="blocks",w.hidden=f!=="regex",E()},g=(()=>{if(Y("os-segmented")&&Y("os-segment")){const C=document.createElement("os-segmented");C.setAttribute("value",i.mode),C.setAttribute("label","How to write the rule"),C.classList.add("atfb-valwin__tabs");for(const[j,B]of _){const le=document.createElement("os-segment");le.setAttribute("value",j),le.textContent=B,C.append(le)}return C.addEventListener("os-pick",j=>{const B=j.detail?.value;(B==="blocks"||B==="regex")&&(C.setAttribute("value",B),u(B))}),C}const f=r("div",{class:"atfb-valwin__tabs",attrs:{role:"tablist"}}),M=()=>{f.replaceChildren(..._.map(([C,j])=>{const B=i.mode===C;return r("button",{class:`atfb-valwin__tab${B?" is-active":""}`,type:"button",text:j,attrs:{role:"tab","aria-selected":B?"true":"false"},on:{click:()=>{u(C),M()}}})}))};return M(),f})();d.hidden=i.mode!=="blocks",w.hidden=i.mode!=="regex";const p=r("input",{class:"atfb-input",value:i.message,placeholder:"That is not in the expected format.",attrs:{type:"text"}});p.addEventListener("input",()=>{i.message=p.value});const x=r("p",{class:"atfb-valwin__summary",attrs:{"aria-live":"polite"}}),I=r("div",{class:"atfb-valwin__samples"}),S=f=>{const M=r("span",{class:"atfb-valwin__verdict",attrs:{"aria-live":"polite"}}),C=r("input",{class:"atfb-input",value:f,placeholder:"Type a sample answer…",attrs:{type:"text"}});C.addEventListener("input",()=>E()),I.append(r("div",{class:"atfb-valwin__sample",children:[C,M]}))};for(const f of i.tests.length?i.tests:["","",""])S(f);const H=()=>Array.from(I.querySelectorAll("input")).map(f=>f.value),E=()=>{const f=Mt(i);x.textContent=f||"Nothing yet — fill in a block above and the rule appears here in plain words.";for(const M of Array.from(I.querySelectorAll(".atfb-valwin__sample"))){const C=M.querySelector("input"),j=M.querySelector(".atfb-valwin__verdict");if(!C||!j)continue;const B=C.value===""?null:kn(i,C.value);j.textContent=B===null?"·":B?"✓ passes":"✗ fails",j.classList.toggle("is-pass",B===!0),j.classList.toggle("is-fail",B===!1)}};n.append(r("p",{class:"atfb-hint",text:`Describe what a good answer to “${e.field.label||"this question"}” looks like — no code needed.`}),g,d,w,r("div",{class:"atfb-valwin__try",children:[r("h3",{text:"Try it out"}),x,I,$("Add another sample",()=>{S(""),E()},"ghost","plus-alt2")]}),y("When it fails, say",p,"Shown to the visitor when their answer breaks the rule. Leave empty for the default wording."),r("div",{class:"atfb-modal__actions",children:[$(a==="window"?"Close without saving":"Cancel",s),$("Save rule",()=>{const f=xe(i);i.tests=H().filter(M=>M!=="").slice(0,10),t.saved=!0,e.onSave({pattern:f,recipe:i,message:i.message.trim()}),s()},"primary")]})),E()}const $n=["Contact","Numbers & codes","Text shape","Web"],Dt=[{slug:"email",label:"An email address",group:"Contact",example:"jane@example.com",pattern:"^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$",message:"That does not look like an email address."},{slug:"phone",label:"A phone number",group:"Contact",example:"+34 612 345 678",pattern:"^(?=(?:[^0-9]*[0-9]){5,})\\+?[0-9 ().-]{5,24}$",message:"That does not look like a phone number."},{slug:"handle",label:"A username or @handle",group:"Contact",example:"@yourname",pattern:"^@?[A-Za-z0-9_]{2,30}$",message:"That does not look like a username."},{slug:"digits",label:"Numbers only",group:"Numbers & codes",example:"12345",pattern:"^[0-9]+$",message:"Numbers only, please."},{slug:"decimal",label:"A number, decimals allowed",group:"Numbers & codes",example:"3.14",pattern:"^-?[0-9]+([.,][0-9]+)?$",message:"That does not look like a number."},{slug:"price",label:"A price",group:"Numbers & codes",example:"19.99",pattern:"^[0-9]+([.,][0-9]{1,2})?$",message:"That does not look like a price."},{slug:"zip_us",label:"A ZIP code (US)",group:"Numbers & codes",example:"90210",pattern:"^[0-9]{5}(-[0-9]{4})?$",message:"That does not look like a ZIP code."},{slug:"postcode_uk",label:"A postcode (UK)",group:"Numbers & codes",example:"SW1A 1AA",pattern:"^[A-Za-z]{1,2}[0-9][A-Za-z0-9]? ?[0-9][A-Za-z]{2}$",message:"That does not look like a postcode."},{slug:"iban",label:"An IBAN",group:"Numbers & codes",example:"DE89 3704 0044 0532 0130 00",pattern:"^[A-Za-z]{2}[0-9]{2}(?: ?[A-Za-z0-9]){10,32}$",message:"That does not look like an IBAN."},{slug:"credit_card",label:"A card number",group:"Numbers & codes",example:"4242 4242 4242 4242",pattern:"^[0-9](?:[0-9 -]{9,21})?[0-9]$",message:"That does not look like a card number.",luhn:!0},{slug:"letters",label:"Letters only",group:"Text shape",example:"María López",pattern:"^[\\p{L}\\p{M} .'’-]+$",message:"Letters only, please."},{slug:"alphanumeric",label:"Letters and numbers only",group:"Text shape",example:"abc123",pattern:"^[A-Za-z0-9]+$",message:"Letters and numbers only, please."},{slug:"no_spaces",label:"One word, no spaces",group:"Text shape",example:"one-word",pattern:"^\\S+$",message:"No spaces allowed."},{slug:"url",label:"A web address",group:"Web",example:"https://example.com",pattern:"^(https?://)?([A-Za-z0-9-]+\\.)+[A-Za-z]{2,}([/?#]\\S*)?$",message:"That does not look like a web address."},{slug:"ip",label:"An IP address",group:"Web",example:"192.168.0.1",pattern:"^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$",message:"That does not look like an IP address."},{slug:"slug",label:"A URL slug",group:"Web",example:"my-page-title",pattern:"^[a-z0-9]+(-[a-z0-9]+)*$",message:"Lowercase letters, numbers and dashes only."},{slug:"hex_color",label:"A hex colour",group:"Web",example:"#3366ff",pattern:"^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$",message:"That does not look like a colour code."}];function An(n){return Dt.find(e=>e.slug===n)??null}function Ye(){return window.wp?.os??null}const qt="allterrain-forms/preview",Ge="allterrain-forms-preview";function Tn(n){const e=Ye();if(!e?.registerTitleBarButton)return()=>{};const t=()=>{try{e.registerTitleBarButton({id:qt,label:"Preview this form",icon:"dashicons-visibility",placement:"right",order:90,match:s=>{const a=s?.id??s?.config?.id??"";return a==="allterrain-forms"||a.startsWith("allterrain-forms#")},onClick:()=>void Ot(n),owner:"allterrain-forms-builder"})}catch{}};return e.ready?e.ready(t):t(),()=>{try{e.unregisterTitleBarButton?.(qt)}catch{}}}async function Ot(n){n.isDirty()&&await n.save();const e=n.current();e&&Nt(e.id,e.title,e.previewUrl)}function Nt(n,e,t){const s=Ye();if(!s?.windowManager?.open){window.open(t,"_blank","noopener");return}s.windowManager.open({id:`${Ge}-${n}`,baseId:Ge,url:t,title:`Preview: ${e}`,icon:"dashicons-visibility"})}function Mn(n,e,t){if(!Ye()?.windowManager?.open||!document.querySelector(`[data-window-id="${Ge}-${n}"]`))return;const i=t.includes("?")?"&":"?";Nt(n,e,`${t}${i}alltfo_r=${Date.now()}`)}const se="allterrain-forms/field",Fn=["openstation/file","desktop-mode/file","openstation/attachment"],Rt="allterrain-forms/logic-map-v2";function J(n,e){return n.dataset.atfbBind=e,n}const Pt={level:{key:"level",label:"Heading level",control:"select",hint:"Headings should step down one at a time.",options:[{value:"2",label:"Heading 2"},{value:"3",label:"Heading 3"},{value:"4",label:"Heading 4"},{value:"5",label:"Heading 5"},{value:"6",label:"Heading 6"}]},content:{key:"content",label:"HTML",control:"textarea",hint:"Shown as written. Scripts are stripped when the form is saved."},consenttext:{key:"consentText",label:"What they are agreeing to",control:"textarea",hint:"Shown beside the tick box. Links are allowed."},height:{key:"height",label:"Height",control:"number",hint:"In pixels."},columns:{key:"columns",label:"Columns",control:"number",hint:"How many pictures sit side by side."},multiple:{key:"multiple",label:"Let them choose more than one",control:"checkbox"},inline:{key:"inline",label:"Lay the options out in a row",control:"checkbox"},other:{key:"other",label:"Offer an “Other” box",control:"checkbox",hint:"Adds a final option with a box to type in."},filetypes:{key:"filetypes",label:"Accepted file types",control:"commas",hint:"Extensions, separated by commas. Empty accepts anything the site allows."},maxsize:{key:"maxsize",label:"Largest file",control:"number",hint:"In megabytes."},maxfiles:{key:"maxfiles",label:"How many files",control:"number"},minrows:{key:"minRows",label:"Fewest rows",control:"number",also:{key:"maxRows",label:"Most rows"}},itemlabel:{key:"itemLabel",label:"What is one row called?",control:"text",hint:"Names each card — “Attendee 1”, “Attendee 2” — and the Remove button. Also editable on the card itself."},endlabels:{key:"minLabel",label:"Label at the low end",control:"text",also:{key:"maxLabel",label:"Label at the high end"}},points:{key:"points",label:"Points if correct",control:"number"},minchoices:{key:"minChoices",label:"Fewest they may pick",control:"number",also:{key:"maxChoices",label:"Most they may pick"}}},In={label:"the canvas edits it in place, and the inspector mirrors it",placeholder:"edited by typing into the control on the canvas",hint:"edited under the control on the canvas",nextlabel:"edited on the page break’s own Next button",prevlabel:"edited on the page break’s own Back button",addlabel:"edited on the repeater’s own Add button",choices:"the choices editor, and the option list on the canvas",required:"the toggle on the card",width:"its own row",css:"its own row",prefill:"its own section",logic:"the conditional logic section",formula:"its own row, with the currency that goes with it",currency:"rendered with the formula",correct:"the choices editor marks the right answer",default:"its own row, typed to match what the field stores",rows:"a row count on a textarea; the statement list on a Likert matrix",parts:"a tick box per part, listed by the server",min:"the validation section, paired with max",max:"the validation section, paired with min",step:"the validation section",minlength:"the validation section, paired with maxlength",maxlength:"the validation section, paired with minlength",mindate:"the validation section, paired with maxdate",maxdate:"the validation section, paired with mindate",mintime:"the validation section, paired with maxtime",maxtime:"the validation section, paired with mintime",pattern:"the validation section",unique:"the validation section",maxchoices:"rendered with minchoices",maxrows:"rendered with minrows"};function Bt(n,e,t){const s=n[e.key],a=i=>t(e.key,i);if(e.control==="checkbox")return O(e.label,!!s,a);if(e.control==="commas"){const i=Array.isArray(s)?s:[];return y(e.label,T(i.join(", "),o=>{a(o.split(",").map(l=>l.trim().replace(/^\./,"").toLowerCase()).filter(Boolean))}),e.hint)}return e.control==="select"?y(e.label,D(String(s??""),e.options??[],a),e.hint):e.control==="textarea"?y(e.label,pe(String(s??""),a),e.hint):e.control==="number"?y(e.label,G(String(s??""),a),e.hint):y(e.label,J(T(String(s??""),a),e.key),e.hint)}function Ht(n,e){const t=new Set(n.map(a=>a.key).filter(Boolean));let s=n.length+1;return e.split(` -`).map(a=>a.trim()).filter(Boolean).map((a,i)=>{const o=n[i]?.key;if(o)return{key:o,label:a};for(;t.has(`r${s}`);)s+=1;return t.add(`r${s}`),{key:`r${s}`,label:a}})}function jt(n,e,t){const s=n.findIndex(i=>i.id===e);if(s<0)return null;const a=Math.max(0,Math.min(t,n.length-1));return a===s?null:{from:s,to:a}}const Xe=[{value:"user:email",label:"Their email address",group:"About the person filling it in",tag:"{user:email}"},{value:"user:display_name",label:"Their name",group:"About the person filling it in",tag:"{user:display_name}"},{value:"user:first_name",label:"Their first name",group:"About the person filling it in"},{value:"user:last_name",label:"Their last name",group:"About the person filling it in"},{value:"user:login",label:"Their username",group:"About the person filling it in"},{value:"date:today",label:"Today’s date",group:"The date and time",tag:"{date}"},{value:"date:now",label:"The time right now",group:"The date and time",tag:"{time}"},{value:"site",label:"This site’s name",group:"About this site",tag:"{site}"},{value:"site:url",label:"This site’s address",group:"About this site",tag:"{site:url}"},{value:"site:admin_email",label:"The site administrator’s email",group:"About this site",tag:"{admin_email}"}],Dn=["About the person filling it in","The date and time","About this site"],Ze=(n,e)=>W?.i18n?.[n]??e;class Wt{constructor(e){this.config=null,this.themes=[],this.forms=[],this.form=null,this.schema=null,this.selected=null,this.tab="build",this.logicMap=null,this.canvasTheme=r("style"),this.canvasThemeSignature="",this.openSections=new Map,this.logicMapOn=is(Rt)!=="off",this.dirty=!1,this.editGeneration=0,this.saveInFlight=!1,this.queuedSave=null,this.teardowns=[],this.canvasTarget=null,this.history=[],this.historyAt=-1,this.autosave=$e(()=>{this.save(!0)},2500),this.renderInspector=as(()=>{if(R(this.inspector),!this.schema||(this.root.classList.toggle("atfb--build-only-panes",this.tab!=="build"),this.tab!=="build"))return;const t=this.selected?this.locateField(this.selected):void 0,s=t?.field,a=t?.parent??null;if(this.root.classList.toggle("atfb--has-selection",!!s),!s){this.inspector.append(r("div",{class:"atfb-placeholder",children:[r("p",{text:"Select a field to change it."}),r("p",{class:"atfb-hint",text:"Drag a field from the palette, or press one to add it to the end."})]}));return}const i=this.config?.fieldTypes.find(c=>c.type===s.type),o=i?.supports??[],l=(c,d)=>{const h=this.liveField(s.id);h&&(h[c]=d,this.markDirty(),this.renderCanvas())};this.inspector.append(r("h3",{class:"atfb-inspector__title",text:i?.label??s.type})),a?this.inspector.append(r("p",{class:"atfb-hint atfb-inspector__crumb",text:`Inside ${a.label||"a repeater"} — the visitor answers this once per ${String(a.itemLabel??"")||"row"}.`}),r("p",{class:"atfb-hint",text:`Formulas aggregate it as {${a.id}.${s.id}} — e.g. sum( {${a.id}.${s.id}} ).`})):this.inspector.append(r("p",{class:"atfb-hint",text:`Reference this field as {field:${s.id}}`})),o.includes("label")&&this.inspector.append(y("Label",J(T(s.label,c=>l("label",c)),"label"))),o.includes("placeholder")&&this.inspector.append(y("Placeholder",J(T(s.placeholder,c=>l("placeholder",c)),"placeholder"))),o.includes("hint")&&this.inspector.append(y("Hint",J(T(s.hint,c=>l("hint",c)),"hint"),"Shown under the field, and read out with it.")),o.includes("nextlabel")&&this.inspector.append(y("Next button",J(T(String(s.nextLabel??""),c=>l("nextLabel",c)),"nextLabel"),"Leave empty for “Next”.")),o.includes("prevlabel")&&this.inspector.append(y("Back button",J(T(String(s.prevLabel??""),c=>l("prevLabel",c)),"prevLabel"),"Leave empty for “Back”.")),o.includes("addlabel")&&this.inspector.append(y("Add button",J(T(String(s.addLabel??""),c=>l("addLabel",c)),"addLabel"),"Leave empty for “Add another”.")),o.includes("required")&&this.inspector.append(O("Required",s.required,c=>l("required",c))),o.includes("width")&&this.inspector.append(y("Width",D(s.width,[{value:"full",label:"Full width"},{value:"two-thirds",label:"Two thirds"},{value:"half",label:"Half"},{value:"third",label:"One third"},{value:"quarter",label:"One quarter"}],c=>l("width",c)))),i?.choices&&this.inspector.append(this.renderChoicesEditor(s,l)),this.renderTypeSettings(s,i,o,l),(s.type==="total"||o.includes("formula"))&&this.inspector.append(y("Formula",r("div",{class:"atfb-formula__row",children:[T(String(s.formula??""),c=>l("formula",c)),$("Formula editor",()=>bn({root:this.root,fields:this.schema?.fields??[],field:s,onSave:c=>{l("formula",c),this.renderInspector()}}))]}),"Reference answers with braces — {f1} * {f2} + 10 — or open the editor and click them in."),y("Currency symbol",T(String(s.currency??""),c=>l("currency",c)))),this.inspector.append(this.renderValidationSection(s,o,l)),a||(this.inspector.append(this.renderLogicSection(s)),o.includes("prefill")&&this.inspector.append(this.prefillControl(s,l))),o.includes("css")&&this.inspector.append(y("CSS class",T(s.cssClass,c=>l("cssClass",c))))}),this.root=e,this.bar=e.querySelector("[data-atfb-bar]")??r("div"),this.palette=e.querySelector("[data-atfb-palette]")??r("div"),this.canvas=e.querySelector("[data-atfb-canvas]")??r("div"),this.inspector=e.querySelector("[data-atfb-inspector]")??r("div"),this.canvas.addEventListener("click",t=>{const s=t.target;if(!(this.tab!=="build"||!this.selected||s.closest("[data-atfb-card], button, os-button, a, input, textarea, select, os-select, label, [contenteditable]"))){this.selected=null;for(const a of this.canvas.querySelectorAll(".atfb-card.is-selected"))a.classList.remove("is-selected");this.renderInspector()}})}async start(){try{const[s,a,i]=await Promise.all([F.config(),F.listThemes(),F.listForms()]);this.config=s,this.themes=a,this.forms=i}catch(s){this.fail(s);return}this.teardowns.push(Jt([se])),this.teardowns.push(Tn({current:()=>this.form?{id:this.form.id,title:this.form.title,previewUrl:this.form.previewUrl}:null,isDirty:()=>this.dirty,save:()=>this.save(!0)}));const e=s=>{this.dirty&&(s.preventDefault(),s.returnValue="")};window.addEventListener("beforeunload",e),this.teardowns.push(()=>window.removeEventListener("beforeunload",e));const t=s=>{!(s.metaKey||s.ctrlKey)||s.key.toLowerCase()!=="z"||s.target?.closest("input, textarea, [contenteditable]")||(s.preventDefault(),this.travel(s.shiftKey?1:-1))};if(this.root.addEventListener("keydown",t),this.teardowns.push(()=>this.root.removeEventListener("keydown",t)),this.renderBar(),this.renderPalette(),this.forms.length){const s=ms("builder");await this.open(s&&this.forms.some(a=>a.id===s)?s:this.forms[0].id)}else this.renderFormsList()}destroy(){this.canvasTarget?.(),this.canvasTarget=null,this.teardowns.forEach(e=>e()),this.teardowns=[]}fail(e){R(this.bar),this.bar.append(r("p",{class:"atfb-error",text:e instanceof Error?e.message:"Something went wrong loading your forms."}))}renderBar(){R(this.bar);const e=D(String(this.form?.id??""),[...this.forms.map(a=>({value:String(a.id),label:a.title||"(untitled)"}))],a=>void this.open(Number(a)));e.setAttribute("aria-label","Choose a form");const t=r("input",{class:"atfb-title",type:"text",value:this.form?.title??"",placeholder:"Untitled form",attrs:{"aria-label":"Form title"},on:{input:a=>{this.form&&(this.form.title=a.target.value,this.markDirty())}}}),s=r("div",{class:"atfb-tabs",attrs:{role:"tablist"},children:[["build","Build"],["theme","Theme"],["settings","Settings"],["notify","Notifications"],["confirm","Confirmations"]].map(([a,i])=>r("button",{class:`atfb-tab${this.tab===a?" is-active":""}`,type:"button",text:i,attrs:{role:"tab","aria-selected":this.tab===a},on:{click:()=>{this.tab=a,this.renderBar(),this.renderInspector(),this.renderCanvas()}}}))});this.bar.append(r("div",{class:"atfb-bar__left",children:[this.forms.length>1?e:null,t]}),s,r("div",{class:"atfb-bar__right",children:[this.historyButton("undo","Undo",-1),this.historyButton("redo","Redo",1),$("New",()=>void this.showTemplates(),"secondary","plus-alt2"),$("Export",()=>void this.exportForm(),"secondary","download"),$("Import",()=>void this.importForm(),"secondary","upload"),$("Preview",()=>void this.preview(),"secondary","visibility"),$("Entries",()=>this.openEntries(),"secondary","list-view"),this.logicMapButton(),this.saveButton()]}))}liveField(e){return this.locateField(e)?.field}locateField(e){const t=this.schema?.fields??[];for(let s=0;sthis.rebindCanvas(),{once:!0});return}this.renderCanvas()}snapshot(){if(!this.schema)return;const e=JSON.stringify(this.schema);this.history[this.historyAt]!==e&&(this.history=this.history.slice(0,this.historyAt+1),this.history.push(e),this.history.length>60&&this.history.shift(),this.historyAt=this.history.length-1,this.syncHistoryButtons())}travel(e){const t=this.historyAt+e;!this.schema||t<0||t>=this.history.length||(this.historyAt=t,this.schema=JSON.parse(this.history[t]),this.schema.fields.some(s=>s.id===this.selected)||(this.selected=null),this.markDirty(),this.renderBar(),this.renderCanvas(),this.renderInspector())}historyButton(e,t,s){const a=$(t,()=>this.travel(s),"secondary",e);return a.setAttribute("data-atfb-history",String(s)),a.disabled=!this.canTravel(s),a}canTravel(e){const t=this.historyAt+e;return t>=0&&tURL.revokeObjectURL(s),1e3)}async importForm(){const e=r("input",{type:"file",attrs:{accept:"application/json,.json"}});e.addEventListener("change",async()=>{const t=e.files?.[0];if(t)try{const s=JSON.parse(await t.text());if(!s.schema)throw new Error("That file does not contain a form.");const a=await F.createForm({title:s.title??t.name.replace(/\.json$/i,""),schema:s.schema});this.forms.unshift({id:a.id,title:a.title,status:a.status,modified:a.modified,fields:a.schema.fields.length,theme:a.schema.settings.theme,entries:0,unread:0,views:0,submissions:0,shortcode:a.shortcode}),this.form=a,this.schema=a.schema,this.selected=null,this.dirty=!1,this.history=[],this.historyAt=-1,this.snapshot(),this.renderBar(),this.renderCanvas(),this.renderInspector(),N("Form imported",a.title)}catch(s){N("Could not import that file",s instanceof Error?s.message:"","error")}}),e.click()}saveButton(){const e=$("Save",()=>void this.save(),"primary");return e.disabled=!this.dirty,e.setAttribute("data-atfb-save",""),e}markDirty(){this.dirty=!0,this.editGeneration+=1;const e=this.bar.querySelector("[data-atfb-save]");e&&(e.disabled=!1),this.autosave()}async save(e=!1){if(!this.form||!this.schema)return;if(this.saveInFlight){this.queuedSave={silent:e&&(this.queuedSave?.silent??!0)};return}this.saveInFlight=!0;const t=this.editGeneration;try{const s=await F.updateForm(this.form.id,{title:this.form.title,schema:this.schema});if(t===this.editGeneration){this.form=s,this.schema=s.schema,this.dirty=!1,this.rebindCanvas();const i=this.bar.querySelector("[data-atfb-save]");i&&(i.disabled=!0)}Ms(s.id);const a=this.forms.find(i=>i.id===s.id);a&&(a.title=s.title),Mn(s.id,s.title,s.previewUrl),e||N("Form saved",s.title)}catch(s){N(Ze("saveFailed","Could not save"),s instanceof Error?s.message:"","error")}finally{this.saveInFlight=!1;const s=this.queuedSave;this.queuedSave=null,s&&this.save(s.silent)}}async openFormById(e){await this.open(e)}async open(e){if(!(this.dirty&&!await ie("You have unsaved changes. Discard them?"))){try{this.form=await F.getForm(e),this.schema=this.form.schema,this.selected=null,this.dirty=!1,this.history=[],this.historyAt=-1,this.snapshot()}catch(t){this.fail(t);return}this.renderBar(),this.renderCanvas(),this.renderInspector(),this.announceIdentity()}}announceIdentity(){this.form&&$t(this.root,_n(this.form,W?.adminUrl??""))}async showTemplates(){if(!this.config)return;const e=r("div",{class:"atfb-overlay"}),t=i=>{i.key==="Escape"&&s()},s=()=>{e.remove(),document.removeEventListener("keydown",t)},a=r("div",{class:"atfb-templates",children:this.config.templates.map(i=>r("button",{class:"atfb-template",type:"button",on:{click:async()=>{s();try{const o=await F.createForm({template:i.slug});this.forms.unshift({id:o.id,title:o.title,status:o.status,modified:o.modified,fields:o.schema.fields.length,theme:o.schema.settings.theme,entries:0,unread:0,views:0,submissions:0,shortcode:o.shortcode}),this.form=o,this.schema=o.schema,this.selected=null,this.dirty=!1,this.history=[],this.historyAt=-1,this.snapshot(),this.renderBar(),this.renderCanvas(),this.renderInspector(),this.announceIdentity()}catch(o){N("Could not create the form",o instanceof Error?o.message:"","error")}}},children:[z(i.icon),r("strong",{text:i.label}),r("span",{text:i.description})]}))});e.append(r("div",{class:"atfb-modal",attrs:{role:"dialog","aria-label":"Start a new form"},children:[r("h2",{text:"Start a new form"}),a,this.archivedFormsSection(s),r("div",{class:"atfb-modal__actions",children:[$("Cancel",s)]})]})),e.addEventListener("click",i=>{i.target===e&&s()}),document.addEventListener("keydown",t),this.root.append(e),a.querySelector("button")?.focus()}archivedFormsSection(e){const t=r("div",{class:"atfb-archived"});return F.listArchivedForms().then(s=>{s.length&&t.append(r("h3",{class:"atfb-archived__title",text:"Or bring one back from the archive"}),...s.map(a=>r("div",{class:"atfb-archived__row",children:[r("div",{class:"atfb-archived__meta",children:[r("strong",{text:a.title||"(untitled)"}),r("span",{class:"atfb-hint",text:`${a.entries} ${a.entries===1?"entry":"entries"} · ${a.submissions} submissions · ${a.views} views`})]}),$("Restore",async()=>{try{const i=await F.unarchiveForm(a.id);e(),this.forms.unshift(i),N("Form restored",`${i.title||"(untitled)"} is back, with its entries and stats.`),await this.open(i.id)}catch(i){N("Could not restore the form",i instanceof Error?i.message:"","error")}},"secondary","undo")]})))}).catch(()=>{}),t}renderFormsList(){R(this.canvas),this.canvas.append(r("div",{class:"atfb-empty",children:[r("h2",{text:"No forms yet"}),r("p",{text:"Start from a template, or build one from nothing."}),$("New form",()=>void this.showTemplates(),"primary","plus-alt2")]}))}openEntries(){const e=window.wp?.os;if(e?.openWindow){e.openWindow("allterrain-forms-entries");return}window.location.assign(`${W?.adminUrl??""}admin.php?page=allterrain-forms-entries`)}renderPalette(){if(!this.config)return;R(this.palette);const e=new Map;for(const s of this.config.fieldTypes){const a=e.get(s.group)??[];a.push(s),e.set(s.group,a)}const t=r("input",{class:"atfb-input atfb-palette__search",type:"search",placeholder:"Search fields",attrs:{"aria-label":"Search field types"},on:{input:s=>{const a=s.target.value.toLowerCase().trim();this.palette.querySelectorAll(".atfb-chip").forEach(i=>{const o=(i.textContent??"").toLowerCase();i.hidden=a!==""&&!o.includes(a)}),this.palette.querySelectorAll(".atfb-group").forEach(i=>{const o=Array.from(i.querySelectorAll(".atfb-chip")).some(l=>!l.hidden);i.hidden=!o})}}});this.palette.append(t);for(const[s,a]of Object.entries(this.config.groups)){const i=e.get(s);i?.length&&this.palette.append(r("div",{class:"atfb-group",children:[r("h3",{class:"atfb-group__title",text:a}),r("div",{class:"atfb-group__items",children:i.map(o=>this.paletteChip(o))})]}))}}paletteChip(e){const t=r("button",{class:"atfb-chip",type:"button",title:e.description,attrs:{"data-atf-type":e.type},children:[z(e.icon),r("span",{text:e.label})]});return t.addEventListener("pointerdown",s=>{const a=r("div",{class:"atfb-chip atfb-chip--ghost",children:[z(e.icon),r("span",{text:e.label})]});Z().start({payload:Ee(se,t,{fieldType:e.type,isNew:!0},s,a),origin:s,onClickOnly:()=>this.addField(e.type)})}),t.addEventListener("click",s=>{Z().recentlyEndedDrag()&&s.preventDefault()}),t}renderCanvas(){if(R(this.canvas),!this.schema||!this.form){this.renderFormsList();return}if(this.tab!=="build"){this.canvas.append(this.renderTabCanvas());return}const e=r("div",{class:"atfb-canvas__list",attrs:{"data-atfb-list":""}});this.schema.fields.length||e.append(r("div",{class:"atfb-placeholder",text:Ze("emptyCanvas","Drag a field from the left to begin.")})),this.schema.fields.forEach((s,a)=>{e.append(this.renderFieldCard(s,a))});const t=r("div",{class:"atfb-canvas__inner",children:[r("p",{class:"atfb-shortcode",text:this.form.shortcode,title:"Paste this anywhere to place the form"}),e]});this.canvas.append(t),this.registerCanvasTarget(e),this.paintLogicMap(t),this.paintCanvasTheme()}prefillControl(e,t){const s=e.prefill.startsWith("query:"),a=Xe.some(_=>_.value===e.prefill),i=s?"query":a&&e.prefill||(e.prefill?"custom":""),o=r("div",{class:"atfb-prefill__detail"}),l=r("p",{class:"atfb-row__hint atfb-prefill__preview"}),c=_=>{if(o.replaceChildren(),l.replaceChildren(),_==="query"){const u=e.prefill.startsWith("query:")?e.prefill.slice(6):"";o.append(T(u,b=>{const g=b.trim();t("prefill",g?`query:${g}`:""),d(`query:${g}`)},"utm_source"),r("p",{class:"atfb-row__hint",text:"The name of the parameter in the link people arrive on."}))}_==="custom"&&o.append(T(e.prefill,u=>{t("prefill",u),d(u)},"myplugin:something"),r("p",{class:"atfb-row__hint",text:"For a source another plugin has added through alltfo_resolve_prefill."})),d(e.prefill)},d=_=>{if(l.replaceChildren(),!_)return;if(_.startsWith("query:")){const b=_.slice(6);b&&(l.textContent=`A visit to …/your-page/?${b}=abc opens the form with “abc” in it.`);return}const u=Xe.find(b=>b.value===_)?.tag;u&&qe(this.form?.id??0).then(b=>{for(const g of b)for(const p of g.items)if(p.tag===u){const x=_.startsWith("user:");if(!p.sample){l.textContent="Empty unless the visitor is signed in.";return}l.textContent=x?`Opens with “${p.sample}” for you — empty for a visitor who is not signed in.`:`Opens with “${p.sample}”.`;return}})};c(i);const h=r("select",{class:"atfb-input atfb-select",on:{change:_=>{const u=_.target.value;t("prefill",u==="query"||u==="custom"?"":u),c(u)}}});h.append(r("option",{value:"",text:"Nothing — leave it empty",attrs:{selected:i===""}}));for(const _ of Dn){const u=document.createElement("optgroup");u.label=_;for(const b of Xe.filter(g=>g.group===_))u.append(r("option",{value:b.value,text:b.label,attrs:{selected:b.value===i}}));h.append(u)}const w=document.createElement("optgroup");return w.label="From the link they arrived on",w.append(r("option",{value:"query",text:"A parameter in the web address",attrs:{selected:i==="query"}})),h.append(w,r("option",{value:"custom",text:"Something else (advanced)",attrs:{selected:i==="custom"}})),y("Pre-fill this with",r("div",{class:"atfb-prefill",children:[h,o,l]}),"What the box already contains when the form opens. They can still change it.")}renderCondition(e,t){const s=t.some(i=>i.kind==="field"&&i.missing),a=r("span",{class:`atfb-cond${s?" is-broken":""}`,attrs:{"aria-label":rt(t)},children:[z("randomize"),...t.map(i=>this.renderConditionToken(e,i))]});return a.addEventListener("pointerdown",i=>i.stopPropagation()),a.addEventListener("click",i=>i.stopPropagation()),a.addEventListener("keydown",i=>i.stopPropagation()),a}editCondition(e,t,s=!0,a=""){const i=this.liveField(e)?.logic;if(i){if(t(i),this.markDirty(),!s){this.logicMap?.setEdges(me(this.schema?.fields??[]));return}this.renderCanvas(),this.renderInspector(),a&&window.requestAnimationFrame(()=>{this.canvas.querySelector(`[data-cond="${CSS.escape(a)}"]`)?.focus()})}}condSelect(e,t,s,a,i){if(Y("os-select")&&Y("os-option")){const o=document.createElement("os-select");o.setAttribute("value",e),o.setAttribute("aria-label",a),o.setAttribute("data-cond",s),o.className="atfb-cond__control",o.title=a;for(const l of t){const c=document.createElement("os-option");c.setAttribute("value",l.value),c.textContent=l.label,o.append(c)}return o.addEventListener("os-pick",l=>{i(String(l.detail?.value??""))}),o}return r("select",{class:"atfb-cond__control atfb-cond__control--native",title:a,attrs:{"aria-label":a,"data-cond":s},on:{change:o=>i(o.target.value)},children:t.map(o=>r("option",{value:o.value,text:o.label,attrs:{selected:o.value===e}}))})}renderConditionToken(e,t){if(t.kind==="field"&&!t.missing)return r("button",{class:"atfb-cond__chip atfb-cond__chip--field",type:"button",text:t.text,title:"Go to this question",on:{click:a=>{a.stopPropagation(),this.selectField(t.fieldId),this.canvas.querySelector(`[data-atfb-card="${CSS.escape(t.fieldId)}"]`)?.scrollIntoView({block:"nearest",behavior:"smooth"})}}});if(t.kind==="verb")return r("button",{class:"atfb-cond__verb",type:"button",text:t.text,title:"Switch between showing and hiding this field when the condition matches.",attrs:{"data-cond":`${e.id}:verb`},on:{click:()=>this.editCondition(e.id,s=>{s.action=s.action==="hide"?"show":"hide"},!0,`${e.id}:verb`)}});if(t.kind==="join")return r("button",{class:"atfb-cond__join",type:"button",text:t.text,title:"Switch between needing every rule (and) or any one of them (or).",attrs:{"data-cond":`${e.id}:join`},on:{click:()=>this.editCondition(e.id,s=>{s.match=s.match==="all"?"any":"all"},!0,`${e.id}:join`)}});if(t.kind==="operator"){const s=`${e.id}:op:${t.ruleIndex}`;return this.condSelect(t.operator,Object.entries(Te).map(([a,i])=>({value:a,label:i})),s,"How the answer is compared",a=>this.editCondition(e.id,i=>{const o=i.rules[t.ruleIndex];o&&(o.operator=a)},!0,s))}return t.kind==="value"?this.renderConditionValue(e,t):r("span",{class:"atfb-cond__chip atfb-cond__chip--missing",text:t.text})}renderConditionValue(e,t){const s=`${e.id}:value:${t.ruleIndex}`,a=this.schema?.fields.find(c=>c.id===t.sourceId),i=(c,d)=>this.editCondition(e.id,h=>{const w=h.rules[t.ruleIndex];w&&(w.value=c)},d,s);if(a?.choices?.length){const c=a.choices.map(d=>({value:d.value,label:d.label||d.value}));return t.raw!==""&&!a.choices.some(d=>d.value===t.raw)&&c.unshift({value:t.raw,label:t.text}),this.condSelect(t.raw,c,s,"The answer that triggers this",d=>i(d,!0))}const o=["number","range","scale","rating","total"].includes(a?.type??""),l=r("input",{class:"atfb-cond__chip atfb-cond__chip--value atfb-cond__value",value:t.raw,title:"The answer that triggers this. Edit it here.",attrs:{type:"text","aria-label":"The answer that triggers this","data-cond":s,inputmode:o?"decimal":void 0,size:String(Math.max(2,Math.min(24,t.raw.length||2)))}});return l.addEventListener("input",()=>{l.size=Math.max(2,Math.min(24,l.value.length||2)),i(l.value,!1)}),l.addEventListener("change",()=>i(l.value,!0)),l.addEventListener("keydown",c=>{c.key==="Enter"&&(c.preventDefault(),i(l.value,!0))}),l}section(e,t,s,a=!1){const i=r("details",{class:"atfb-section",attrs:{open:this.openSections.get(e)??a},children:[r("summary",{text:t}),...s]});return i.addEventListener("toggle",()=>this.openSections.set(e,i.open)),i}logicMapButton(){const e=me(this.schema?.fields??[]).length>0,t=$(this.logicMapOn?"Hide logic":"Show logic",()=>{this.logicMapOn=!this.logicMapOn,rs(Rt,this.logicMapOn?"on":"off"),this.renderBar(),this.renderCanvas()},this.logicMapOn?"primary":"secondary","randomize");return t.title="Draw a line from each question to the ones it decides.",t.hidden=!e,t}paintLogicMap(e){this.logicMap?.destroy(),this.logicMap=null;const t=this.schema?.fields??[],s=me(t);if(!s.length||!this.logicMapOn)return;e.classList.add("has-logicmap");const a=new ws(e);a.setEdges(s),a.highlight(this.selected??""),this.logicMap=a,e.addEventListener("pointerover",i=>{const o=i.target.closest("[data-atfb-card]");a.highlight(o?.dataset.atfbCard??this.selected??"")}),e.addEventListener("pointerleave",()=>a.highlight(this.selected??""))}renderFieldCard(e,t){const s=this.config?.fieldTypes.find(d=>d.type===e.type),a=this.selected===e.id,i=this.schema?.fields??[],o=gs(e,i),l=ys(i).get(e.id)??0,c=r("div",{class:`atfb-card${a?" is-selected":""}`,attrs:{"data-atfb-card":e.id,"data-index":t,tabindex:"0",role:"button","aria-pressed":a,"aria-label":`${e.label||s?.label||e.type}, ${t+1} of ${this.schema?.fields.length??0}`},children:[r("div",{class:"atfb-card__bar",children:[r("div",{class:"atfb-card__grip",attrs:{"aria-hidden":"true"},children:[z("menu")]}),r("div",{class:"atfb-card__head",children:[z(s?.icon??"dashicons-forms"),r("span",{class:"atfb-card__type",text:s?.label??e.type}),this.requiredToggle(e),l?r("span",{class:"atfb-badge atfb-badge--controls",text:l===1?"controls 1 field":`controls ${l} fields`,title:"Other questions appear or disappear based on this answer."}):null]}),r("div",{class:"atfb-card__actions",children:[this.cardAction("admin-page","Duplicate",()=>this.duplicateField(e.id)),this.cardAction("trash","Delete",()=>void this.deleteField(e.id))]})]}),r("div",{class:"atfb-card__body",children:[dt(e,s,{edit:d=>{const h=this.liveField(e.id);if(!h)return;d(h),this.markDirty(),this.syncInspector(h);const w=this.selected?this.locateField(this.selected):void 0;w?.parent&&w.parent.id===h.id&&this.syncInspector(w.field)},restructure:d=>{const h=this.liveField(e.id);h&&(this.snapshot(),d(h),this.markDirty(),this.renderCanvas(),this.renderInspector())},types:d=>this.config?.fieldTypes.find(h=>h.type===d),selectedId:this.selected}),o.length?this.renderCondition(e,o):null]})]});return c.addEventListener("click",d=>{d.target.closest(".atfb-card__actions")||Z().recentlyEndedDrag()||this.selectField(e.id)}),c.addEventListener("keydown",d=>{if(d.key==="Enter"||d.key===" "){d.preventDefault(),this.selectField(e.id);return}d.altKey&&(d.key==="ArrowUp"||d.key==="ArrowDown")&&(d.preventDefault(),this.moveField(e.id,d.key==="ArrowUp"?t-1:t+1),window.requestAnimationFrame(()=>{this.canvas.querySelector(`[data-atfb-card="${CSS.escape(e.id)}"]`)?.focus()})),(d.key==="Delete"||d.key==="Backspace")&&(d.preventDefault(),this.deleteField(e.id))}),c.addEventListener("pointerdown",d=>{d.target.closest(".atfb-card__actions")||Z().start({payload:Ee(se,c,{fieldId:e.id,field:e,isNew:!1},d),origin:d})}),c}requiredToggle(e){return r("button",{class:`atfb-req${e.required?" is-on":""}`,type:"button",text:e.required?"Required":"Optional",title:e.required?"This must be answered. Click to make it optional.":"Click to make this required.",attrs:{"aria-pressed":e.required?"true":"false"},on:{pointerdown:s=>s.stopPropagation(),click:s=>{s.stopPropagation(),e.required=!e.required,this.markDirty(),this.renderCanvas(),this.renderInspector()}}})}cardAction(e,t,s){return r("button",{class:"atfb-card__action",type:"button",title:t,attrs:{"aria-label":t},on:{click:a=>{a.stopPropagation(),s()}},children:[z(e)]})}gapAnimator(e,t,s,a){const i=r("div",{class:"atfb-marker",attrs:{"aria-hidden":"true"}});let o=null,l=null,c=0;const d=()=>{i.remove(),l?.classList.remove("atfb-lifted"),l=null,o=null},h=u=>{const b=Array.from(e.querySelectorAll(t)),g=new Map(b.filter(x=>!x.classList.contains("atfb-lifted")).map(x=>[x,x.getBoundingClientRect().top]));u();for(const x of b)x.style.transition="none",x.style.transform="";const p=b.filter(x=>{const I=g.get(x);if(I===void 0||!x.isConnected||x.classList.contains("atfb-lifted"))return!1;const S=I-x.getBoundingClientRect().top;return Math.abs(S)<.5?!1:(x.style.transform=`translateY(${S}px)`,!0)});e.offsetHeight;for(const x of b){if(!p.includes(x)){x.style.transition="";continue}x.style.transition="transform 160ms ease",x.style.transform="",x.addEventListener("transitionend",()=>{x.style.transition=""},{once:!0})}},w=u=>{const b=u.detail;if(b?.payload?.type!==se||!e.classList.contains("is-dropping")||a&&!a(b.payload.data??{}))return;const g=b.payload.data?.fieldId,p=typeof g=="string"?s(g):null,x=Je(e,t,b.clientY??0,p??void 0);x===o&&i.isConnected||(o=x,h(()=>{p&&p!==l&&(l?.classList.remove("atfb-lifted"),p.classList.add("atfb-lifted"),l=p),i.style.blockSize=`${Math.max(8,Math.round(c))}px`;const I=Array.from(e.querySelectorAll(t)).filter(S=>S!==p);x>=I.length?e.append(i):I[x].before(i)}))},_=()=>{e.classList.remove("is-dropping"),window.setTimeout(()=>h(d),0)};return document.addEventListener("os.drag.move",w),document.addEventListener("os.drag.end",_),{enter:u=>{const b=u?s(u):null;c=b&&!b.classList.contains("atfb-lifted")?b.getBoundingClientRect().height:c||44},leave:()=>h(d),drop:(u,b)=>{const g=o??Je(e,t,u,b??void 0);return d(),g},teardown:()=>{document.removeEventListener("os.drag.move",w),document.removeEventListener("os.drag.end",_),d()}}}registerCanvasTarget(e){this.canvasTarget?.(),this.canvasTarget=null;const t=this.gapAnimator(e,".atfb-card",i=>this.canvas.querySelector(`[data-atfb-card="${CSS.escape(i)}"]`)),s=Z().registerDropTarget({id:`atfb-canvas-${this.form?.id??0}`,element:e,accept:i=>i.type===se,onEnter:i=>{e.classList.add("is-dropping"),t.enter(i.payload.data.fieldId)},onLeave:()=>{e.classList.remove("is-dropping"),t.leave()},onDrop:(i,o)=>{e.classList.remove("is-dropping");const l=i.payload.data,c=l.fieldId?this.canvas.querySelector(`[data-atfb-card="${CSS.escape(l.fieldId)}"]`):null,d=t.drop(o.clientY,c);if(l.isNew&&l.fieldType){this.addField(l.fieldType,d);return}if(l.fieldId&&this.locateField(l.fieldId)){this.relocateField(l.fieldId,null,d);return}l.field&&this.insertField({...l.field,id:""},d)}}),a=this.wireRepeaterZones(e);this.canvasTarget=()=>{s(),a.forEach(i=>i()),t.teardown()}}wireRepeaterZones(e){const t=[];return e.querySelectorAll("[data-atfb-repeater-zone]").forEach(s=>{const a=s.dataset.atfbRepeaterZone??"",i=l=>{const c=l;if(c.fieldId===a)return!1;const d=c.isNew?c.fieldType:this.locateField(c.fieldId??"")?.field.type??c.field?.type;return!!d&&this.allowedInRepeater(d)},o=this.gapAnimator(s,".atfb-subcard",l=>e.querySelector(`[data-atfb-subfield="${CSS.escape(l)}"]`),i);t.push(()=>o.teardown()),t.push(Z().registerDropTarget({id:`atfb-repzone-${this.form?.id??0}-${a}`,element:s,accept:l=>l.type===se&&i(l.data),onEnter:l=>{s.classList.add("is-dropping"),o.enter(l.payload.data.fieldId)},onLeave:()=>{s.classList.remove("is-dropping"),o.leave()},onDrop:(l,c)=>{s.classList.remove("is-dropping");const d=l.payload.data,h=d.fieldId?e.querySelector(`[data-atfb-subfield="${CSS.escape(d.fieldId)}"]`):null,w=o.drop(c.clientY,h);if(d.isNew&&d.fieldType){this.addFieldToRepeater(d.fieldType,a,w);return}if(d.fieldId&&this.locateField(d.fieldId)){this.relocateField(d.fieldId,a,w);return}d.field&&this.insertFieldIntoRepeater({...d.field,id:""},a,w)}})),s.querySelectorAll(".atfb-subcard").forEach(l=>{const c=l.dataset.atfbSubfield??"";l.addEventListener("click",d=>{d.target.closest(".atfb-preview__remove")||(d.stopPropagation(),!Z().recentlyEndedDrag()&&this.selectField(c))}),l.addEventListener("keydown",d=>{if(d.stopPropagation(),d.key==="Enter"||d.key===" "){d.preventDefault(),this.selectField(c);return}if(d.altKey&&(d.key==="ArrowUp"||d.key==="ArrowDown")){d.preventDefault();const h=this.locateField(c);h&&(this.relocateField(c,a,d.key==="ArrowUp"?h.index-1:h.index+1),window.requestAnimationFrame(()=>{this.canvas.querySelector(`[data-atfb-subfield="${CSS.escape(c)}"]`)?.focus()}));return}(d.key==="Delete"||d.key==="Backspace")&&(d.preventDefault(),this.deleteField(c))}),l.addEventListener("pointerdown",d=>{if(d.stopPropagation(),d.target.closest(".atfb-preview__remove"))return;const h=this.locateField(c);h&&Z().start({payload:Ee(se,l,{fieldId:c,parentId:a,field:h.field,isNew:!1},d),origin:d})})})}),t}addField(e,t){const s=this.buildField(e);s&&this.insertField(s,t)}buildField(e){const t=this.config?.fieldTypes.find(a=>a.type===e);return!t||!this.schema?void 0:{id:this.nextFieldId(),type:e,label:t.input?t.label:"",placeholder:"",hint:"",required:!1,width:"full",cssClass:"",default:"",choices:t.choices?[{label:"First choice",value:"first"},{label:"Second choice",value:"second"}]:[],logic:{enabled:!1,action:"show",match:"all",rules:[]},messages:{},prefill:"",...t.settings}}allowedInRepeater(e){const t=this.config?.fieldTypes.find(s=>s.type===e);return!t||!t.input?!1:!["repeater","page_break","file","signature","total"].includes(e)}addFieldToRepeater(e,t,s){if(!this.allowedInRepeater(e))return;const a=this.buildField(e);a&&this.insertFieldIntoRepeater(a,t,s)}insertFieldIntoRepeater(e,t,s){const a=this.liveField(t);if(!this.schema||!a||a.type!=="repeater")return;e.id||(e.id=this.nextFieldId()),this.snapshot(),Array.isArray(a.fields)||(a.fields=[]);const i=a.fields,o=s===void 0?i.length:Math.max(0,Math.min(s,i.length));i.splice(o,0,e),this.selected=e.id,this.markDirty(),this.renderCanvas(),this.renderInspector(),window.requestAnimationFrame(()=>{this.canvas.querySelector(`[data-atfb-subfield="${CSS.escape(e.id)}"]`)?.focus()})}relocateField(e,t,s){if(!this.schema)return;const a=this.locateField(e);if(!a)return;if(!a.parent&&!t){this.moveField(e,s);return}const i=t?this.liveField(t):null;if(t&&(!i||i.type!=="repeater"||e===t))return;i&&!Array.isArray(i.fields)&&(i.fields=[]);const o=i?i.fields:this.schema.fields;this.snapshot(),a.list.splice(a.index,1);const l=Math.max(0,Math.min(s,o.length));o.splice(l,0,a.field),this.selected=a.field.id,this.markDirty(),this.renderCanvas(),this.renderInspector()}insertField(e,t){if(!this.schema)return;e.id||(e.id=this.nextFieldId()),this.snapshot();const s=t===void 0?this.schema.fields.length:Math.max(0,Math.min(t,this.schema.fields.length));this.schema.fields.splice(s,0,e),this.selected=e.id,this.markDirty(),this.renderCanvas(),this.renderInspector(),window.requestAnimationFrame(()=>{this.canvas.querySelector(`[data-atfb-card="${CSS.escape(e.id)}"]`)?.focus()})}moveField(e,t){if(!this.schema)return;const s=jt(this.schema.fields,e,t);if(!s)return;this.snapshot();const[a]=this.schema.fields.splice(s.from,1);this.schema.fields.splice(s.to,0,a),this.markDirty(),this.renderCanvas()}duplicateField(e){if(!this.schema)return;const t=this.locateField(e);if(!t)return;const s=JSON.parse(JSON.stringify(t.field)),a=this.usedFieldIds(),i=()=>{let o=a.size+1;for(;a.has(`f${o}`);)o++;const l=`f${o}`;return a.add(l),l};s.id=i();for(const o of s.fields??[])o.id=i();if(t.parent){this.snapshot(),t.list.splice(t.index+1,0,s),this.selected=s.id,this.markDirty(),this.renderCanvas(),this.renderInspector();return}this.insertField(s,t.index+1)}async deleteField(e){if(!this.schema)return;const t=this.locateField(e);if(!t)return;const s=this.schema.fields.filter(i=>i.logic?.rules?.some(o=>o.field===e)),a=s.length?`Delete this field? ${s.length} other field${s.length===1?"":"s"} use it in a condition, and those conditions will stop working.`:Ze("confirmDelete","Delete this? It cannot be undone.");await ie(a,"Delete field")&&(this.snapshot(),t.list.splice(t.index,1),this.selected===e&&(this.selected=null),this.markDirty(),this.renderCanvas(),this.renderInspector())}selectField(e){this.selected=e;for(const t of this.canvas.querySelectorAll("[data-atfb-card]")){const s=t.dataset.atfbCard===e;t.classList.toggle("is-selected",s),t.setAttribute("aria-pressed",s?"true":"false")}for(const t of this.canvas.querySelectorAll("[data-atfb-subfield]"))t.classList.toggle("is-selected",t.dataset.atfbSubfield===e);this.logicMap?.highlight(e),this.renderInspector()}nextFieldId(){const e=this.usedFieldIds();let t=e.size+1;for(;e.has(`f${t}`);)t++;return`f${t}`}usedFieldIds(){const e=new Set;for(const t of this.schema?.fields??[]){e.add(t.id);for(const s of t.fields??[])e.add(s.id)}return e}nextEntryId(e,t){const s=new Set(t.map(i=>i.id));let a=t.length+1;for(;s.has(`${e}${a}`);)a++;return`${e}${a}`}renderTypeSettings(e,t,s,a){for(const i of s){const o=Pt[i];o&&(this.inspector.append(Bt(e,o,a)),o.also&&this.inspector.append(Bt(e,{...o,key:o.also.key,label:o.also.label,hint:o.also.hint},a)))}s.includes("rows")&&e.type!=="likert"&&this.inspector.append(y("Lines tall",G(String(e.rows??""),i=>a("rows",i)))),s.includes("rows")&&e.type==="likert"&&this.inspector.append(this.renderStatementList(e,a)),s.includes("parts")&&this.inspector.append(this.renderPartsEditor(e,t,a)),s.includes("default")&&!["files","object"].includes(t?.value??"")&&this.inspector.append(this.renderDefaultControl(e,t,a))}renderStatementList(e,t){const s=Array.isArray(e.rows)?e.rows:[];return y("Statements",pe(s.map(a=>a.label??"").join(` +var allTerrainFormsBuilder=function(ee){"use strict";class Zt{constructor(){this.targets=[],this.active=null,this.lastEndMs=0}start(e){if(this.active||e.origin.button!==0)return null;const{payload:t,origin:s}=e,a=s.clientX,i=s.clientY;let o=!1,l=!1,c=null,d=null,h=0,w=0;const _=()=>{document.removeEventListener("pointermove",x),document.removeEventListener("pointerup",I),document.removeEventListener("pointercancel",S),document.removeEventListener("keydown",H),window.removeEventListener("blur",S),c?.remove(),c=null,t.source.classList.remove("atf-is-dragging"),document.body.classList.remove("atf-drag-active"),d?.onLeave?.(b),d=null,this.active=null,this.lastEndMs=Date.now()},u=(E,f)=>{document.dispatchEvent(new CustomEvent(E,{detail:{payload:t,clientX:f?.clientX,clientY:f?.clientY}}))},b={payload:t,isFinished:()=>l,cancel:(E="caller")=>{l||(l=!0,_(),o&&u("os.drag.end"),e.onCancel?.(E))}},g=E=>{o=!0,t.source.classList.add("atf-is-dragging"),document.body.classList.add("atf-drag-active");const f=t.source.getBoundingClientRect();h=t.ghost?.offsetX??a-f.left,w=t.ghost?.offsetY??i-f.top,c=t.ghost?.element??t.source.cloneNode(!0),c.classList.add("atf-drag-ghost"),c.style.width=`${f.width}px`,document.body.appendChild(c),p(E),u("os.drag.start",E)},p=E=>{c&&(c.style.transform=`translate3d(${E.clientX-h}px, ${E.clientY-w}px, 0)`)},x=E=>{if(l)return;if(!o){if(Math.hypot(E.clientX-a,E.clientY-i)<4)return;g(E)}p(E);const f=this.hitTest(E.clientX,E.clientY);f!==d&&(d?.onLeave?.(b),d=f,d?.onEnter?.(b)),u("os.drag.move",E)},I=E=>{if(l)return;if(!o){l=!0,_(),e.onClickOnly?.();return}const f=d;if(l=!0,_(),f&&f.accept(t)){e.onCommit?.(f),f.onDrop(b,{clientX:E.clientX,clientY:E.clientY}),u("os.drag.end",E);return}u("os.drag.end",E),e.onCancel?.(f?"rejected":"no-target")},S=()=>b.cancel("pointercancel"),H=E=>{E.key==="Escape"&&b.cancel("escape")};return document.addEventListener("pointermove",x),document.addEventListener("pointerup",I),document.addEventListener("pointercancel",S),document.addEventListener("keydown",H),window.addEventListener("blur",S),this.active=b,b}registerDropTarget(e){return this.targets=this.targets.filter(t=>t.id!==e.id),this.targets.push(e),()=>{this.targets=this.targets.filter(t=>t.id!==e.id)}}isDragging(){return this.active!==null}recentlyEndedDrag(e=500){return Date.now()-this.lastEndMso.right||to.bottom)continue;const l=Kt(i.element);if(l>a){s=i,a=l;continue}l===a&&s&&Vt(i.element,s.element)&&(s=i)}return s}}function Kt(n){let e=0,t=n;for(;t;)e++,t=t.parentElement;return e}function Vt(n,e){return(e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_FOLLOWING)!==0}function Se(){return window.wp?.os??null}let Le=null;function Z(){const n=Se();return n?.dragManager?n.dragManager:(Le||(Le=new Zt),Le)}function Jt(n){const e=i=>{const o=i.detail?.payload;return o&&n.includes(o.type)?o.source:null},t=i=>i.preventDefault(),s=i=>{const o=e(i);o&&(o.classList.add("atf-is-dragging"),document.body.classList.add("atf-drag-active"),window.getSelection()?.removeAllRanges(),document.addEventListener("selectstart",t))},a=i=>{const o=e(i);o&&(o.classList.remove("atf-is-dragging"),document.body.classList.remove("atf-drag-active"),document.removeEventListener("selectstart",t))};return document.addEventListener("os.drag.start",s),document.addEventListener("os.drag.end",a),()=>{document.removeEventListener("os.drag.start",s),document.removeEventListener("os.drag.end",a),document.removeEventListener("selectstart",t)}}const Qt=["atfb","atfe","atfs","atfm"];function Ee(n,e,t,s,a){const i=e.getBoundingClientRect();if(!a){const o=Qt.find(l=>e.closest(`.${l}`));if(o){const l=e.cloneNode(!0);l.style.transition="",l.style.transform="",a=document.createElement("div"),a.className=`${o} atf-ghost-scope`,a.appendChild(l)}}return a&&(a.style.width=`${Math.round(i.width)}px`,a.style.maxWidth=`${Math.round(i.width)}px`,a.style.boxSizing="border-box"),{type:n,source:e,data:t,ghost:{element:a,offsetX:s.clientX-i.left,offsetY:s.clientY-i.top,hint:{neutral:"",accept:"",reject:"",hidden:!0}}}}function Je(n,e,t,s){const a=Array.from(n.querySelectorAll(e)).filter(i=>i!==s&&!i.classList.contains("atf-drag-ghost"));for(let i=0;ia.fetch(l,c,{source:"allterrain-forms"}):(l,c)=>fetch(l,c))(t,{credentials:"same-origin",...e,headers:s});if(!o.ok){let l=`Request failed with status ${o.status}.`,c="";try{const d=await o.json();l=d.message??l,c=d.code??""}catch{}throw new de(l,o.status,c)}if(o.status!==204)return await o.json()}const U=n=>Ce(n);async function es(n){if(!j?.wpRestUrl)throw new de("AllTerrain Forms is not configured on this page.",0);const e=j.nonce?{"X-WP-Nonce":j.nonce}:{},t=Se(),s=Qe(j.wpRestUrl,n),a=t?.fetch?await t.fetch(s,{credentials:"same-origin",headers:e},{source:"allterrain-forms"}):await fetch(s,{credentials:"same-origin",headers:e});if(!a.ok)throw new de(`Request failed with status ${a.status}.`,a.status);return await a.json()}function ts(n){if(!n||!n.includes("&"))return n;const e=document.createElement("textarea");return e.innerHTML=n,e.value}const K=(n,e)=>Ce(n,{method:"POST",body:JSON.stringify(e)}),he=n=>Ce(n,{method:"DELETE"});function et(n){const e=new URLSearchParams;for(const[s,a]of Object.entries(n))a===void 0||a===""||a===!1||e.set(s,String(a));const t=e.toString();return t?`?${t}`:""}function ue(n){const e=n?.schema?.settings;return e&&(!e.themeOverrides||Array.isArray(e.themeOverrides))&&(e.themeOverrides={...e.themeOverrides}),n}const F={config:()=>U("/config"),mailpoet:()=>U("/mailpoet"),listForms:()=>U("/forms"),listArchivedForms:()=>U("/forms?archived=1"),archiveForm:n=>K(`/forms/${n}/archive`,{}),unarchiveForm:n=>K(`/forms/${n}/unarchive`,{}),getForm:n=>U(`/forms/${n}`).then(ue),createForm:n=>K("/forms",n).then(ue),updateForm:(n,e)=>K(`/forms/${n}`,e).then(ue),duplicateForm:n=>K(`/forms/${n}/duplicate`,{}).then(ue),deleteForm:n=>he(`/forms/${n}`),preview:(n,e)=>K(`/forms/${n}/preview`,e),pages:()=>es("wp/v2/pages?per_page=100&status=publish&orderby=title&order=asc&_fields=id,title").then(n=>n.map(e=>({id:e.id,title:ts(e.title?.rendered??"")||`#${e.id}`}))),mergeTags:n=>U(`/forms/${n}/merge-tags`).then(e=>e.groups),analytics:(n,e="")=>U(`/forms/${n}/analytics${e?`?dimension=${encodeURIComponent(e)}`:""}`),demoStatus:()=>U("/demo"),demoSeed:n=>K("/demo",n?{count:n}:{}),demoRemove:()=>he("/demo"),listEntries:n=>U(`/entries${et(n)}`),getEntry:n=>U(`/entries/${n}`),updateEntry:(n,e)=>K(`/entries/${n}`,e),deleteEntry:n=>he(`/entries/${n}`),exportEntries:n=>U(`/entries/export${et(n)}`),listThemes:()=>U("/themes"),saveTheme:n=>K("/themes",n),deleteTheme:n=>he(`/themes/${n}`)};function r(n,e={}){const t=document.createElement(n);e.class&&(t.className=e.class),e.text!==void 0&&(t.textContent=e.text),e.html!==void 0&&(t.innerHTML=e.html),e.title&&(t.title=e.title),e.type&&"type"in t&&(t.type=e.type),e.value!==void 0&&"value"in t&&(t.value=e.value),e.placeholder!==void 0&&"placeholder"in t&&(t.placeholder=e.placeholder),e.href&&"href"in t&&(t.href=e.href);for(const[s,a]of Object.entries(e.attrs??{}))a===void 0||a===!1||t.setAttribute(s,a===!0?"":String(a));Object.assign(t.style,e.style??{});for(const[s,a]of Object.entries(e.on??{}))t.addEventListener(s,a);for(const s of e.children??[])s==null||s===!1||t.append(s);return t}function R(n){n.replaceChildren()}function z(n){return r("span",{class:`dashicons ${n.startsWith("dashicons-")?n:`dashicons-${n}`}`,attrs:{"aria-hidden":"true"}})}function y(n,e,t){const s=e.matches("input, select, textarea, button")?e:e.querySelector("input, select, textarea");if(s){const a=s.id||`atf-c-${Math.random().toString(36).slice(2,9)}`;return s.id=a,r("div",{class:"atfb-row",children:[r("label",{class:"atfb-row__label",text:n,attrs:{for:a}}),e,t?r("p",{class:"atfb-row__hint",text:t}):null]})}return r("div",{class:"atfb-row",children:[r("span",{class:"atfb-row__label",text:n}),e,t?r("p",{class:"atfb-row__hint",text:t}):null]})}function T(n,e,t=""){return r("input",{class:"atfb-input",type:"text",value:n,placeholder:t,on:{input:s=>e(s.target.value)}})}function G(n,e){if(Y("os-number-field")){const t=document.createElement("os-number-field");return t.setAttribute("value",n),t.classList.add("atfb-field"),t.addEventListener("os-input-change",s=>{e(String(s.detail?.value??""))}),t}return r("input",{class:"atfb-input",type:"number",value:n,on:{input:t=>e(t.target.value)}})}function pe(n,e,t=4){const s=r("textarea",{class:"atfb-input atfb-input--area",attrs:{rows:t},on:{input:a=>e(a.target.value)}});return s.value=n,s}function D(n,e,t){if(Y("os-select")&&Y("os-option")){const s=document.createElement("os-select");s.setAttribute("value",n),s.classList.add("atfb-field");for(const a of e){const i=document.createElement("os-option");i.setAttribute("value",a.value),i.textContent=a.label,s.append(i)}return s.addEventListener("os-pick",a=>{t(String(a.detail?.value??""))}),s}return r("select",{class:"atfb-input atfb-select",on:{change:s=>t(s.target.value)},children:e.map(s=>r("option",{value:s.value,text:s.label,attrs:{selected:s.value===n}}))})}function O(n,e,t){if(Y("os-checkbox-label")){const a=document.createElement("os-checkbox-label");return a.setAttribute("label",n),a.classList.add("atfb-check"),e&&a.setAttribute("checked",""),a.addEventListener("os-checkbox-change",i=>{t(!!i.detail?.checked)}),a}const s=r("input",{type:"checkbox",on:{change:a=>t(a.target.checked)}});return s.checked=e,r("label",{class:"atfb-check",children:[s,r("span",{text:n})]})}function $(n,e,t="secondary",s){const a=[s?z(s):null,r("span",{text:n})];if(Y("os-button")){const i=document.createElement("os-button");i.setAttribute("variant",t),i.setAttribute("type","button"),i.classList.add("atfb-button",`atfb-button--${t}`),i.addEventListener("click",e);for(const o of a)o&&i.append(o);return i}return r("button",{class:`atfb-button atfb-button--${t}`,type:"button",on:{click:e},children:a})}function Y(n){return typeof customElements<"u"&&!!customElements.get(n)}const ss=["os-button","os-checkbox-label","os-number-field","os-select","os-option","os-segmented","os-segment","os-color-field","os-empty-state"];let fe=null;function ns(){if(fe)return fe;const n=window.wp?.os;return fe=n?.loadComponents?n.loadComponents(ss).catch(()=>{}):Promise.resolve(),fe}async function ie(n,e=""){const t=window.wp?.os;return t?.confirm?t.confirm({title:e,message:n,danger:!0}):window.confirm(n)}function N(n,e="",t="info"){const s=window.wp?.os;if(s?.notify){s.notify({title:n,body:e,type:t});return}console.info(`[AllTerrain Forms] ${n}${e?`: ${e}`:""}`)}function as(n){let e=0,t;return(...s)=>{t=s,!e&&(e=window.requestAnimationFrame(()=>{e=0,n(...t)}))}}function $e(n,e){let t=0;return(...s)=>{window.clearTimeout(t),t=window.setTimeout(()=>n(...s),e)}}function is(n){try{return window.localStorage.getItem(n)??""}catch{return""}}function rs(n,e){try{window.localStorage.setItem(n,e)}catch{}}function Ae(n){const e=n.closest(".os-window__body");e&&(e.addEventListener("scroll",()=>{e.scrollTop&&(e.scrollTop=0),e.scrollLeft&&(e.scrollLeft=0)},{passive:!0}),e.scrollTop=0,e.scrollLeft=0)}function tt(){const n=[window];try{window.parent&&window.parent!==window&&n.push(window.parent)}catch{}return n}function os(n){const e=n.wp?.os?.windowManager?.getAll?.()??[];for(const t of e){const s=t.iframe??t.element?.querySelector?.("iframe");if(s&&s.contentWindow===window)return t}return null}const ls=4e3,cs=100;function ds(n,e){const t=Date.now()+ls,s=()=>{const a=os(n);if(a&&a.id!==e){typeof a.close=="function"?a.close():n.wp?.os?.windowManager?.remove?.(a.id);return}!a&&Date.now()0?{form:t}:void 0;t>0&&fs(t);for(const a of tt())if(a.wp?.os?.openWindow?.(e,{source:"handoff",params:s})){window.setTimeout(()=>ds(a,e),0);return}}function us(){document.addEventListener("click",n=>{const e=n.target?.closest?.("[data-atf-open-window]");if(!e)return;n.preventDefault();const t=e.getAttribute("data-atf-open-window")??"";for(const s of tt())if(s.wp?.os?.openWindow?.(t,{source:"handoff"}))return})}const ps="allterrain-forms/requested-form";function fs(n){try{window.sessionStorage.setItem(ps,String(n))}catch{}}function st(n){return`allterrain-forms/requested-form-${n}`}function ms(n){try{const e=window.sessionStorage.getItem(st(n));return window.sessionStorage.removeItem(st(n)),Number(e)||0}catch{return 0}}const Te={is:"is",is_not:"is not",contains:"contains",not_contains:"does not contain",starts_with:"starts with",ends_with:"ends with",greater:"is more than",less:"is less than",greater_equal:"is at least",less_equal:"is at most",empty:"is empty",not_empty:"has any answer"},Me=["empty","not_empty"];function nt(n,e){const t=n.find(s=>s.id===e);return t?t.label||"an untitled question":null}function at(n,e,t){return n.find(i=>i.id===e)?.choices?.find(i=>i.value===t)?.label||t}function bs(n,e){const t=Te[n.operator]??String(n.operator);if(Me.includes(n.operator))return t;const s=at(e,n.field,n.value);return`${n.operator==="is"?"":`${t} `}${s!==""?s:"(nothing)"}`}function it(n,e,t=0){const s=nt(e,n.field),a=Te[n.operator]??String(n.operator);if(s===null)return[{kind:"field",text:"a question that no longer exists",fieldId:n.field,missing:!0}];const i=[{kind:"field",text:s,fieldId:n.field,missing:!1},{kind:"operator",text:a,operator:n.operator,ruleIndex:t}];if(Me.includes(n.operator))return i;const o=at(e,n.field,n.value);return i.push({kind:"value",text:o!==""?o:"(nothing)",raw:n.value,sourceId:n.field,ruleIndex:t}),i}function gs(n,e){const t=n.logic;if(!t?.enabled||!t.rules.length)return[];const s=[{kind:"verb",text:t.action==="hide"?"Hidden when":"Shown when"}];return t.rules.forEach((a,i)=>{i>0&&s.push({kind:"join",text:t.match==="all"?"and":"or"}),s.push(...it(a,e,i))}),s}function rt(n){return n.map(e=>e.text).join(" ")}function vs(n,e){return rt(it(n,e))}function me(n){const e=[];for(const t of n){const s=t.logic;if(s?.enabled)for(const a of s.rules)!a.field||a.field===t.id||e.push({from:a.field,to:t.id,label:vs(a,n),short:bs(a,n),action:s.action,broken:nt(n,a.field)===null})}return e}function ys(n){const e=new Map,t=new Set;for(const s of me(n)){const a=`${s.from}->${s.to}`;s.broken||t.has(a)||(t.add(a),e.set(s.from,(e.get(s.from)??0)+1))}return e}const ot=10,lt=22,ne="http://www.w3.org/2000/svg";class ws{constructor(e){this.edges=[],this.teardowns=[],this.frame=0,this.host=e,this.svg=document.createElementNS(ne,"svg"),this.svg.setAttribute("class","atfb-logicmap"),this.svg.setAttribute("aria-hidden","true"),this.svg.setAttribute("focusable","false"),e.append(this.svg);const t=()=>this.schedule();window.addEventListener("resize",t),this.teardowns.push(()=>window.removeEventListener("resize",t));const s=e.closest(".atfb__canvas")??e;if(s.addEventListener("scroll",t,{passive:!0}),this.teardowns.push(()=>s.removeEventListener("scroll",t)),typeof ResizeObserver<"u"){const a=new ResizeObserver(t);a.observe(e),this.teardowns.push(()=>a.disconnect())}}setEdges(e){this.edges=e,this.schedule()}highlight(e){this.svg.classList.toggle("is-focused",!!e),this.svg.querySelectorAll("[data-from]").forEach(t=>{const s=e&&(t.dataset.from===e||t.dataset.to===e);t.classList.toggle("is-lit",!!s)})}destroy(){this.teardowns.forEach(e=>e()),this.teardowns=[],this.svg.remove(),this.frame&&cancelAnimationFrame(this.frame)}schedule(){this.frame||(this.frame=requestAnimationFrame(()=>{this.frame=0,this.draw()}))}draw(){this.svg.replaceChildren();const e=this.host.getBoundingClientRect();this.svg.setAttribute("viewBox",`0 0 ${e.width} ${e.height}`),this.svg.setAttribute("width",String(e.width)),this.svg.setAttribute("height",String(e.height));const t=new Map;for(const s of this.edges){const a=this.cardRect(s.from),i=this.cardRect(s.to);if(!a||!i)continue;const o=t.get(s.from)??0;t.set(s.from,o+1),this.drawEdge(s,a,i,e,o)}}cardRect(e){const t=this.host.querySelector(`[data-atfb-card="${CSS.escape(e)}"]`);return t?t.getBoundingClientRect():null}drawEdge(e,t,s,a,i){const o=t.right-a.left,l=t.top+t.height/2-a.top,c=s.right-a.left,d=s.top+s.height/2-a.top,h=Math.max(0,a.width-ot-Math.max(o,c)),w=22+i*11+Math.min(22,Math.abs(d-l)/8),_=Math.max(12,Math.min(w,h*.4)),u=document.createElementNS(ne,"path");u.setAttribute("d",`M ${o} ${l} C ${o+_} ${l}, ${c+_} ${d}, ${c} ${d}`),u.setAttribute("class",`atfb-logicmap__path is-${e.action}${e.broken?" is-broken":""}`),u.dataset.from=e.from,u.dataset.to=e.to;const b=document.createElementNS(ne,"title");b.textContent=e.label,u.append(b);const g=document.createElementNS(ne,"circle");g.setAttribute("cx",String(c)),g.setAttribute("cy",String(d)),g.setAttribute("r","3.5"),g.setAttribute("class",`atfb-logicmap__dot is-${e.action}${e.broken?" is-broken":""}`),g.dataset.from=e.from,g.dataset.to=e.to,this.svg.append(u,g);const p=document.createElementNS(ne,"text");p.setAttribute("x",String(a.width-ot)),p.setAttribute("y",String(d+3)),p.setAttribute("text-anchor","end"),p.setAttribute("class","atfb-logicmap__label"),p.dataset.from=e.from,p.dataset.to=e.to,p.textContent=e.short.length>lt?`${e.short.slice(0,lt-1)}…`:e.short;const x=document.createElementNS(ne,"title");x.textContent=e.label,p.append(x),this.svg.append(p)}}const _s={text:"text",email:"text",url:"text",tel:"text",number:"text",password:"text",date:"text",time:"text",datetime:"text",date_range:"composite",textarea:"textarea",select:"select",country:"select",multiselect:"options",radio:"options",checkboxes:"options",image_choice:"options",quiz:"options",switch:"toggle",consent:"toggle",file:"file",range:"range",rating:"summary",scale:"summary",likert:"summary",signature:"summary",repeater:"repeater",color:"text",name:"composite",address:"composite",total:"text",hidden:"static",heading:"static",html:"static",divider:"static",spacer:"static",page_break:"static"};function xs(n){return _s[n]??"text"}function ct(n,e){const t=/^choice:(\d+):(label|value)$/.exec(e);return String(t?n.choices?.[Number(t[1])]?.[t[2]]??"":n[e]??"")}function V(n){const{value:e,onInput:t,onCommit:s}=n,a=r("span",{class:`${n.class} atfb-editable`,text:e,attrs:{contenteditable:"plaintext-only",role:"textbox",spellcheck:"false","data-placeholder":n.placeholder}});return n.bind&&(a.dataset.atfbBind=n.bind),a.addEventListener("pointerdown",i=>i.stopPropagation()),a.addEventListener("input",()=>t(a.textContent??"")),a.addEventListener("keydown",i=>{i.key==="Enter"&&(i.preventDefault(),a.blur()),i.key==="Escape"&&(a.textContent=e,a.blur()),i.stopPropagation()}),a.addEventListener("blur",()=>s?.()),a}function ks(n){const e=r("input",{class:"atf-choice__input",type:n==="checkboxes"||n==="multiselect"?"checkbox":"radio"});return e.disabled=!0,e}function dt(n,e,t){const s=xs(n.type),a=V({value:n.label,placeholder:"Write the question…",class:"atf-label",bind:"label",onInput:o=>t.edit(l=>{l.label=o}),onCommit:()=>t.restructure(()=>{})}),i=[s==="static"||s==="toggle"?null:a,Ls(n,e,s,t),Ss(n,e,t)];return r("div",{class:"atfb-preview atf-form",children:[r("div",{class:`atf-field atf-field--${n.type}`,children:i})]})}function Ss(n,e,t){if(!e?.supports.includes("hint"))return null;const s=V({value:n.hint??"",placeholder:"Add a hint…",class:"atf-hint",bind:"hint",onInput:a=>t.edit(i=>{i.hint=a})});return s.setAttribute("aria-label","Hint"),r("p",{class:"atfb-preview__hint",children:[s]})}function Ls(n,e,t,s){switch(t){case"text":return Fe(n,"atf-input",s);case"textarea":return Fe(n,"atf-input atf-textarea",s,!0);case"select":return r("div",{class:"atfb-preview__select",children:[Fe(n,"atf-input atf-select",s)]});case"options":return As(n,s);case"toggle":return r("div",{class:n.type==="switch"?"atf-toggle atf-toggle--switch":"atf-toggle",children:[(()=>{const a=r("input",{class:"atf-toggle__input",type:"checkbox"});return a.disabled=!0,a})(),V({value:n.label||"",placeholder:n.type==="consent"?"What are they agreeing to?…":"What does this turn on?…",class:"atf-toggle__label",bind:"label",onInput:a=>s.edit(i=>{i.label=a}),onCommit:()=>s.restructure(()=>{})})]});case"file":return r("div",{class:"atf-file",children:[r("input",{class:"atf-file__input",type:"file"})]});case"range":return r("input",{class:"atf-range__input",type:"range"});case"composite":return r("div",{class:"atf-composite__parts",children:[r("span",{class:"atf-input atfb-preview__ghost",text:""}),r("span",{class:"atf-input atfb-preview__ghost",text:""})]});case"static":return Ts(n,e,s);case"repeater":return Es(n,s);default:return r("div",{class:"atfb-preview__stack",children:[r("p",{class:"atfb-preview__summary",text:`${e?.label??n.type} — set this up in the panel on the right.`})]})}}function Es(n,e){const t=n.fields??[],s=r("div",{class:"atfb-repeater__zone",attrs:{"data-atfb-repeater-zone":n.id}});return t.forEach(a=>s.append(Cs(n,a,e))),t.length||s.append(r("p",{class:"atfb-repeater__empty",text:"Drag fields from the palette in here — the visitor gets a fresh copy of each with every row they add."})),r("div",{class:"atfb-repeater",attrs:{"data-atfb-repeater":n.id},children:[r("div",{class:"atf-repeater__row atfb-repeater__frame",children:[r("div",{class:"atf-repeater__row-head",children:[r("span",{class:"atf-repeater__title atfb-repeater__title",children:[V({value:String(n.itemLabel??""),placeholder:"Row",class:"atfb-repeater__item-label",bind:"itemLabel",onInput:a=>e.edit(i=>{i.itemLabel=a})}),r("span",{class:"atfb-repeater__ordinal",text:"1",attrs:{"aria-hidden":"true"}})]})]}),s]}),$s(n,e)]})}function Cs(n,e,t){const s=e.id,a=l=>c=>{const d=(c.fields??[]).find(h=>h.id===s);d&&l(d)},i={edit:l=>t.edit(a(l)),restructure:l=>t.restructure(a(l)),types:t.types,selectedId:t.selectedId},o=t.types?.(e.type);return r("div",{class:`atfb-subcard${t.selectedId===s?" is-selected":""}`,attrs:{"data-atfb-subfield":s,"data-atfb-parent":n.id,tabindex:"0",role:"button","aria-label":`${e.label||o?.label||e.type}, inside ${n.label||"the repeater"}`},children:[r("div",{class:"atfb-subcard__head",children:[r("span",{class:"atfb-subcard__type",text:o?.label??e.type}),e.required?r("span",{class:"atfb-subcard__required",text:"Required"}):null,r("button",{class:"atfb-preview__remove",type:"button",title:"Remove this field from the repeater",attrs:{"aria-label":`Remove ${e.label||o?.label||"this field"}`},on:{pointerdown:l=>l.stopPropagation(),click:l=>{l.stopPropagation(),t.restructure(c=>{const d=c.fields??[],h=d.findIndex(w=>w.id===s);h>=0&&d.splice(h,1)})}},children:[r("span",{text:"×"})]})]}),dt(e,o,i)]})}function Fe(n,e,t,s=!1){const a=V({value:n.placeholder??"",placeholder:n.type==="select"||n.type==="country"?"Choose…":"Placeholder…",class:`${e} atfb-preview__box${s?" atfb-preview__box--tall":""}`,bind:"placeholder",onInput:i=>t.edit(o=>{o.placeholder=i})});return a.setAttribute("aria-label","Placeholder"),a}function Ie(n,e,t,s,a,i){return r("span",{class:`atf-button ${t} atfb-preview__button`,children:[V({value:n,placeholder:e,class:"atfb-preview__button-text",bind:s,onInput:o=>a.edit(l=>i(l,o))})]})}function $s(n,e){return Ie(String(n.addLabel??""),"Add another","atf-button--ghost atfb-repeater__add","addLabel",e,(t,s)=>{t.addLabel=s})}function As(n,e){const t=r("div",{class:"atf-choices__list"});return(n.choices??[]).forEach((s,a)=>{t.append(r("div",{class:"atf-choice atfb-preview__option",children:[ks(n.type),V({value:s.label,placeholder:"Option…",class:"atf-choice__label",bind:`choice:${a}:label`,onInput:i=>{e.edit(o=>{const l=o.choices?.[a];if(!l)return;const c=!l.value||l.value===l.label;l.label=i,c&&(l.value=i)})}}),r("button",{class:"atfb-preview__remove",type:"button",title:"Remove this option",attrs:{"aria-label":`Remove ${s.label||"this option"}`},on:{pointerdown:i=>i.stopPropagation(),click:i=>{i.stopPropagation(),e.restructure(o=>{o.choices.splice(a,1)})}},children:[r("span",{text:"×"})]})]}))}),t.append(r("button",{class:"atfb-preview__add",type:"button",on:{pointerdown:s=>s.stopPropagation(),click:s=>{s.stopPropagation(),e.restructure(a=>{const i=a.choices.length+1;a.choices.push({label:`Option ${i}`,value:`Option ${i}`})})}},children:[r("span",{text:"+ Add option"})]})),r("fieldset",{class:"atf-choices",children:[t]})}function Ts(n,e,t){return n.type==="heading"?V({value:n.label,placeholder:"Section heading…",class:"atf-heading",bind:"label",onInput:s=>t.edit(a=>{a.label=s}),onCommit:()=>t.restructure(()=>{})}):n.type==="divider"?r("hr",{class:"atf-divider"}):n.type==="page_break"?r("div",{class:"atfb-preview__stack",children:[r("p",{class:"atfb-progress-name",children:[V({value:n.label,placeholder:"Name this step…",class:"atf-progress__label",bind:"label",onInput:s=>t.edit(a=>{a.label=s}),onCommit:()=>t.restructure(()=>{})})]}),r("p",{class:"atfb-preview__summary",text:"Everything after this is a new page."}),r("div",{class:"atf-nav atfb-preview__nav",children:[Ie(String(n.prevLabel??""),"Back","atf-button--secondary","prevLabel",t,(s,a)=>{s.prevLabel=a}),Ie(String(n.nextLabel??""),"Next","","nextLabel",t,(s,a)=>{s.nextLabel=a})]})]}):r("p",{class:"atfb-preview__summary",text:`${e?.label??n.type} — nothing is shown to the visitor here.`})}const De=new Map;function qe(n){let e=De.get(n);return e||(e=F.mergeTags(n).catch(()=>[]),De.set(n,e)),e}function Ms(n){De.delete(n)}function Fs(n){const e=new Map;for(const t of n)for(const s of t.items)e.set(s.tag,s);return e}function Is(n,e){const t=Fs(e);return n.replace(/\{[a-z_]+(?::[^}]*)?\}/gi,s=>{const a=t.get(s.toLowerCase());return a?a.sample:s})}function Ds(n){return/\{[a-z_]+(?::[^}]*)?\}/i.test(n)}let te=null;function re(){te?.remove(),te=null}typeof document<"u"&&(document.addEventListener("pointerdown",n=>{const e=n.target;e?.closest(".atfb-tagpick__open")||te&&!te.contains(e)&&re()}),document.addEventListener("keydown",n=>{n.key==="Escape"&&te&&(re(),n.stopPropagation())}));function ht(n,e){const t=n.selectionStart??n.value.length,s=n.selectionEnd??n.value.length;n.value=n.value.slice(0,t)+e+n.value.slice(s);const a=t+e.length;n.setSelectionRange(a,a),n.focus(),n.dispatchEvent(new Event("input",{bubbles:!0}))}function qs(n){let e=n.parentElement;for(;e&&e!==document.body;){const t=getComputedStyle(e).overflowY;if(t==="auto"||t==="scroll"||t==="hidden")return e.getBoundingClientRect().bottom;e=e.parentElement}return window.innerHeight}function Os(n,e){const t=r("input",{class:"atfb-input atfb-tagpick__search",type:"search",placeholder:"Search values…",attrs:{"aria-label":"Search values"}}),s=r("div",{class:"atfb-tagpick__list"}),a=i=>{s.replaceChildren();const o=i.trim().toLowerCase();let l=0;for(const c of n){const d=c.items.filter(h=>!o||h.label.toLowerCase().includes(o)||h.tag.toLowerCase().includes(o));if(!d.length){c.empty&&!o&&!c.items.length&&s.append(r("p",{class:"atfb-tagpick__group",text:c.label}),r("p",{class:"atfb-tagpick__empty",text:c.empty}));continue}s.append(r("p",{class:"atfb-tagpick__group",text:c.label}));for(const h of d)l+=1,s.append(r("button",{class:"atfb-tagpick__item",type:"button",on:{click:()=>{e(h.tag),re()}},children:[r("span",{class:"atfb-tagpick__item-main",children:[r("span",{class:"atfb-tagpick__label",text:h.label}),r("code",{class:"atfb-tagpick__tag",text:h.tag})]}),h.hint||h.sample?r("span",{class:"atfb-tagpick__meta",text:h.sample?`e.g. ${h.sample}`:h.hint}):null]}))}!l&&o&&s.append(r("p",{class:"atfb-tagpick__empty",text:`Nothing matches “${i}”.`}))};return a(""),t.addEventListener("input",()=>a(t.value)),r("div",{class:"atfb-tagpick",attrs:{role:"dialog","aria-label":"Insert a value"},children:[r("p",{class:"atfb-tagpick__intro",text:"Pick something to drop in. It is filled in when the form is submitted."}),t,s]})}function ut(n,e){const t=r("button",{class:"atfb-button atfb-button--ghost atfb-tagpick__open",type:"button",title:"Insert a value from the submission",children:[z("shortcode"),r("span",{text:"Insert a value"})]}),s=r("div",{class:"atfb-taggable",children:[n,r("div",{class:"atfb-taggable__tools",children:[t]})]}),a=e.preview===!1?null:r("p",{class:"atfb-taggable__preview"});a&&s.append(a);const i=()=>{if(a){if(!Ds(n.value)){a.textContent="",a.hidden=!0;return}qe(e.formId).then(o=>{a.hidden=!1,a.replaceChildren(r("span",{class:"atfb-taggable__preview-label",text:"Reads as"}),r("span",{text:Is(n.value,o)}))})}};return n.addEventListener("input",i),i(),t.addEventListener("click",o=>{if(o.stopPropagation(),te&&s.contains(te)){re();return}re(),qe(e.formId).then(l=>{const c=Os(l,d=>{ht(n,d),i()});s.append(c),te=c,(window.innerHeight-c.getBoundingClientRect().topqs(s))&&c.classList.add("atfb-tagpick--above"),c.querySelector(".atfb-tagpick__search")?.focus()})}),s}function pt(n,e){const t=parseFloat(String(n??""));return Number.isFinite(t)?t:e}function ft(n,e){let t=e[0];for(const s of e)Math.abs(s.at-n)n.accent??"#2271b1",apply:n=>({accent:n,"accent-soft":`color-mix( in srgb, ${n} 12%, transparent )`,"border-focus":n,"focus-ring-color":n,"button-bg":n,"button-bg-hover":`color-mix( in srgb, ${n} 88%, #000 )`})},{id:"roundness",label:"Roundness",hint:"Corners, everywhere at once.",kind:"scale",steps:Oe.map(({value:n,label:e})=>({value:n,label:e})),owns:["radius-field","radius-button","radius-card","radius-check"],read:n=>ft(pt(n["radius-field"],4),Oe),apply:n=>{const e=Oe.find(t=>t.value===n)?.at??4;return{"radius-field":`${e}px`,"radius-button":`${e}px`,"radius-card":`${Math.min(e*2,28)}px`,"radius-check":`${Math.min(e,6)}px`}}},{id:"density",label:"Density",hint:"How much air the form has.",kind:"scale",steps:Ne.map(({value:n,label:e})=>({value:n,label:e})),owns:["pad-field-x","pad-field-y","gap-fields","gap-label","button-pad-x","button-pad-y"],read:n=>ft(pt(n["pad-field-y"],9),Ne),apply:n=>{const e=Ne.find(t=>t.value===n)?.at??9;return{"pad-field-y":`${e}px`,"pad-field-x":`${Math.round(e*1.4)}px`,"gap-fields":`${Math.round(e*2.2)}px`,"gap-label":`${Math.max(4,Math.round(e*.7))}px`,"button-pad-y":`${e+2}px`,"button-pad-x":`${Math.round(e*2.2)}px`}}},{id:"shadow",label:"Depth",hint:"How far the form sits off the page.",kind:"scale",steps:Ns,owns:["shadow-field","shadow-field-focus","shadow-button","shadow-button-hover","shadow-card"],read:n=>{const e=n["shadow-card"]??"none";return e==="none"||e.trim()===""?n["shadow-field"]&&n["shadow-field"]!=="none"?"hard":"none":e.includes("0 1px")||e.includes("2px")?"subtle":"lifted"},apply:n=>{switch(n){case"subtle":return{"shadow-field":"none","shadow-field-focus":"none","shadow-button":"0 1px 2px rgba( 0, 0, 0, 0.12 )","shadow-button-hover":"0 2px 4px rgba( 0, 0, 0, 0.16 )","shadow-card":"0 1px 3px rgba( 0, 0, 0, 0.1 )"};case"lifted":return{"shadow-field":"none","shadow-field-focus":"0 4px 12px rgba( 0, 0, 0, 0.12 )","shadow-button":"0 4px 10px rgba( 0, 0, 0, 0.16 )","shadow-button-hover":"0 8px 20px rgba( 0, 0, 0, 0.2 )","shadow-card":"0 10px 30px rgba( 0, 0, 0, 0.14 )"};case"hard":return{"shadow-field":"3px 3px 0 currentColor","shadow-field-focus":"5px 5px 0 currentColor","shadow-button":"4px 4px 0 currentColor","shadow-button-hover":"2px 2px 0 currentColor","shadow-card":"none"};default:return{"shadow-field":"none","shadow-field-focus":"none","shadow-button":"none","shadow-button-hover":"none","shadow-card":"none"}}}},{id:"field-style",label:"Fields",hint:"How an input is drawn.",kind:"choice",steps:Rs,owns:["field-style"],read:n=>n["field-style"]??"outline",apply:n=>({"field-style":n})},{id:"labels",label:"Labels",hint:"Where the question sits relative to the answer.",kind:"choice",steps:Ps,owns:["label-position"],read:n=>n["label-position"]??"top",apply:n=>({"label-position":n})}]}function Bs(n){return Re().find(e=>e.owns.includes(n))??null}function mt(n){let e=n.activeSlug,t={...n.overrides},s=n.themes;const a=m=>{t=m,n.onOverridesReplaced?.({...m})},i=r("div",{class:"atfs-preview__frame"}),o=r("div",{class:"atfs-controls__body"}),l=r("div",{class:"atfs-quick"}),c=r("div",{class:"atfs-themes"}),d=$e(async()=>{try{const m=await n.previewFor(e,t),v=i.closest(".atf-studio__preview"),k=v?.scrollTop??0;i.innerHTML=m,v&&(v.scrollTop=k),document.dispatchEvent(new CustomEvent("alltfo-refresh"))}catch(m){R(i),i.append(r("p",{class:"atfb-error",text:m instanceof Error?m.message:"Preview failed."}))}},180),h={"label-position":"atf-labels-","field-style":"atf-fields-"},w=(m,v,k)=>{const L=k.replace(/[^a-z0-9_-]/gi,"");for(const A of[...m.classList])A.startsWith(v)&&m.classList.remove(A);L&&m.classList.add(v+L)},_=m=>{const v=i.querySelector(".atf-form-wrap"),k=v?.querySelector(".atf-form");if(v)for(const[L,A]of Object.entries(m))A===""?v.style.removeProperty(`--atf-${L}`):v.style.setProperty(`--atf-${L}`,A),k&&h[L]&&w(k,h[L],A||(s.find(P=>P.slug===e)?.resolved[L]??""))},u=m=>{_(m.resolved);const v=i.querySelector(".atf-form");v&&(w(v,"atf-theme-",m.slug),v.classList.toggle("atf-is-dark",!!m.dark))},b=m=>t[m.token]!==void 0?t[m.token]:s.find(k=>k.slug===e)?.resolved?.[m.token]??m.default,g=()=>{R(c);for(const m of s){const v=r("button",{class:`atfs-theme${m.slug===e?" is-active":""}`,type:"button",attrs:{"aria-pressed":m.slug===e,title:m.description},children:[r("span",{class:"atfs-theme__chip",style:{background:m.resolved.surface??"#fff",borderColor:m.resolved.border??"#ccc",borderRadius:m.resolved["radius-field"]??"4px",boxShadow:m.resolved["shadow-card"]??"none"},children:[r("span",{class:"atfs-theme__accent",style:{background:m.resolved.accent??"#2271b1",borderRadius:m.resolved["radius-button"]??"4px"}})]}),r("span",{class:"atfs-theme__name",text:m.label}),m.custom?r("span",{class:"atfb-badge",text:"yours"}):null],on:{click:()=>{e=m.slug,a({}),n.onTheme(e),u(m),g(),S(),I(),Yt()}}});c.append(v)}},p=()=>{const v={...s.find(k=>k.slug===e)?.resolved??{}};for(const[k,L]of Object.entries(t))v[k]=L;return v},x=m=>{const v=l.closest(".atf-studio__controls"),k=v?.scrollTop??0;m(),v&&(v.scrollTop=k)},I=$e(()=>x(f),150),S=()=>{const m=p(),v=l.querySelectorAll(".atfs-dial");Re().forEach((k,L)=>{const A=v[L];if(!A)return;const P=k.read(m);if(k.kind==="colour"){const q=A.querySelector('input[type="color"]'),X=A.querySelector("input.atfb-input");q&&document.activeElement!==q&&(q.value=be(P)),X&&document.activeElement!==X&&(X.value=P);return}A.querySelectorAll(".atfs-segment").forEach((q,X)=>{const Q=k.steps?.[X]?.value===P;q.classList.toggle("is-on",Q),q.setAttribute("aria-pressed",String(Q))})})},H=(m,v)=>{const k=m.apply(v,p());for(const[L,A]of Object.entries(k))t[L]=A,n.onOverride(L,A);_(k),S(),I()},E=()=>{R(l);const m=p();for(const v of Re()){const k=v.read(m);let L;if(v.kind==="colour"){const A=r("input",{class:"atfs-color",type:"color",value:be(k),attrs:{"aria-label":v.label},on:{input:P=>H(v,P.target.value)}});L=r("div",{class:"atfs-color-row",children:[A,T(k,P=>H(v,P))]})}else L=r("div",{class:`atfs-segmented atfs-segmented--${v.kind}`,attrs:{role:"group","aria-label":v.label},children:(v.steps??[]).map(A=>r("button",{class:`atfs-segment${A.value===k?" is-on":""}`,type:"button",text:A.label,attrs:{"aria-pressed":A.value===k},on:{click:()=>H(v,A.value)}}))});l.append(r("div",{class:"atfs-dial",children:[r("div",{class:"atfs-dial__head",children:[r("span",{class:"atfs-dial__label",text:v.label}),r("span",{class:"atfs-dial__hint",text:v.hint})]}),L]}))}},f=()=>{R(o);const m=new Map;for(const k of n.tokens){const L=m.get(k.group)??[];L.push(k),m.set(k.group,L)}const v={colour:"Colour",shape:"Corners and borders",shadow:"Shadows",fields:"Field style",space:"Spacing",type:"Type",labels:"Labels",button:"Buttons",focus:"Focus ring",motion:"Motion"};for(const[k,L]of m)o.append(r("details",{class:"atfs-group",attrs:{open:k==="colour"},children:[r("summary",{text:v[k]??k}),...L.map(A=>M(A))]}))},M=m=>{const v=b(m),k=q=>{q===""?delete t[m.token]:t[m.token]=q,n.onOverride(m.token,q),_({[m.token]:q})};let L;if(m.control==="color"){const q=r("input",{class:"atfs-color",type:"color",value:be(v),attrs:{"aria-label":`${m.label} colour`},on:{input:Q=>{const ce=Q.target.value;X.value=ce,k(ce)}}}),X=T(v,Q=>{q.value=be(Q),k(Q)});L=r("div",{class:"atfs-color-row",children:[q,X]})}else if(m.control==="select")L=D(v,(m.options??[]).map(q=>({value:q,label:q})),k);else if(m.control==="length"){const q=parseFloat(v)||0,X=m.unit??"px",Q=m.token.includes("radius")?60:80,ce=r("input",{class:"atfs-range",type:"range",value:String(q),attrs:{min:"0",max:String(Q),step:"1","aria-label":m.label},on:{input:ke=>{const Xt=`${ke.target.value}${X}`;Gt.value=Xt,k(Xt)}}}),Gt=T(v,ke=>{ce.value=String(parseFloat(ke)||0),k(ke)});L=r("div",{class:"atfs-length-row",children:[ce,Gt]})}else L=T(v,k);const A=y(m.label,L),P=Bs(m.token);return P&&(A.classList.add("is-dialled"),A.append(r("span",{class:"atfs-owned",text:P.label}))),t[m.token]!==void 0&&(A.classList.add("is-overridden"),A.append(r("button",{class:"atfs-reset",type:"button",text:"Reset",attrs:{"aria-label":`Reset ${m.label} to the theme's value`},on:{click:()=>{k(""),x(f)}}}))),A},C=async()=>{const m=s.find(L=>L.slug===e),v=m?`${m.label} (mine)`:"My theme",k=window.prompt("Name this theme",v);if(k)try{const L={};for(const P of n.tokens){const q=b(P);q!==P.default&&(L[P.token]=q)}const A=await F.saveTheme({label:k,tokens:L});s=[...s.filter(P=>P.slug!==A.slug),A],n.onThemesChanged(s),e=A.slug,a({}),n.onTheme(e),g(),S(),x(f),N("Theme saved",A.label)}catch(L){N("Could not save the theme",L instanceof Error?L.message:"","error")}},W=async()=>{const m=s.find(v=>v.slug===e);if(m?.custom&&await ie(`Delete “${m.label}”? Forms using it fall back to Clean.`,"Delete theme"))try{await F.deleteTheme(m.id),s=s.filter(k=>k.slug!==m.slug),n.onThemesChanged(s),e="clean",a({}),n.onTheme(e),g(),S(),x(f);const v=s.find(k=>k.slug===e);v&&u(v)}catch(v){N("Could not delete the theme",v instanceof Error?v.message:"","error")}},B=async()=>{const m=s.find(L=>L.slug===e),v={label:m?.label??e,tokens:{...m?.tokens??{},...t}},k=JSON.stringify(v,null," ");try{await navigator.clipboard.writeText(k),N("Theme copied","Paste it into another site to import it.")}catch{window.prompt("Copy this theme",k)}},le=()=>{const m=window.prompt("Paste a theme");if(m)try{const v=JSON.parse(m);if(!v.tokens||typeof v.tokens!="object")throw new Error("That does not look like a theme.");for(const[k,L]of Object.entries(v.tokens))t[k]=String(L),n.onOverride(k,String(L));_({...t}),S(),x(f)}catch(v){N("Could not read that theme",v instanceof Error?v.message:"","error")}},zt=$("Delete",()=>void W(),"danger"),Yt=()=>{const m=s.find(v=>v.slug===e);zt.hidden=!m?.custom};return g(),E(),f(),d(),Yt(),r("div",{class:"atf-studio",children:[r("div",{class:"atf-studio__top",children:[r("div",{class:"atf-studio__topbar",children:[r("h2",{class:"atf-studio__heading",text:"Theme"}),r("div",{class:"atfs__actions",children:[$("Save as a theme",()=>void C(),"primary"),$("Export",()=>void B()),$("Import",le),zt]})]}),c]}),r("div",{class:"atf-studio__panes",children:[r("aside",{class:"atf-studio__controls",children:[r("p",{class:"atfb-hint",text:"Changes apply to this form only, until you save them as a theme."}),l,r("details",{class:"atfs-advanced",children:[r("summary",{text:"Every setting"}),r("p",{class:"atfb-hint",text:"The full token list. The dials above write into it."}),o]})]}),r("main",{class:"atf-studio__preview",children:[r("h2",{class:"screen-reader-text",text:"Live preview"}),i]})]})]})}async function Pe(){const n=document.querySelector("[data-atfs-root]");if(!n||n.dataset.atfsMounted)return;n.dataset.atfsMounted="1",Ae(n);const e=n.querySelector("[data-atfs-bar]"),t=n.querySelector(".atfs__body")??n;try{const[s,a,i]=await Promise.all([F.config(),F.listThemes(),F.listForms()]);if(!i.length){R(t),t.append(r("div",{class:"atfb-empty",children:[r("h2",{text:"No forms to preview"}),r("p",{text:"Make a form first — a theme needs something to dress."})]}));return}let o=i[0],l={},c=o.theme;const d=()=>{R(t),t.append(mt({themes:a,tokens:s.tokens,activeSlug:c,overrides:l,standalone:!0,onTheme:h=>{c=h},onOverride:(h,w)=>{w===""?delete l[h]:l[h]=w},onOverridesReplaced:h=>{l={...h}},previewFor:async(h,w)=>{const _=await F.getForm(o.id);_.schema.settings.theme=h,_.schema.settings.themeOverrides=w;const{html:u}=await F.preview(o.id,{schema:_.schema,theme:h});return u},onThemesChanged:h=>{a.length=0,a.push(...h)}}))};e&&(R(e),e.append(r("span",{class:"atfs__label",text:"Preview against"}),D(String(o.id),i.map(h=>({value:String(h.id),label:h.title||"(untitled)"})),h=>{o=i.find(w=>String(w.id)===h)??i[0],l={},d()}))),d()}catch(s){R(t),t.append(r("p",{class:"atfb-error",text:s instanceof Error?s.message:"Could not load themes."}))}}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>void Pe()):Pe(),document.addEventListener("os-window-content-loaded",()=>void Pe()));function be(n){const e=n.trim();return/^#[0-9a-f]{6}$/i.test(e)?e:/^#[0-9a-f]{3}$/i.test(e)?`#${e[1]}${e[1]}${e[2]}${e[2]}${e[3]}${e[3]}`:"#888888"}const Hs="mailpoet",ge="",Ws=["checkbox","checkboxes","toggle","consent","gdpr"],bt=["email"];function gt(n){return n.schema.actions.find(e=>e.type==="mailpoet")}function Be(n){const e=n?.settings??{};return{lists:Array.isArray(e.lists)?e.lists.map(Number).filter(Boolean):[],email_field:typeof e.email_field=="string"?e.email_field:"",first_name_field:typeof e.first_name_field=="string"?e.first_name_field:"",last_name_field:typeof e.last_name_field=="string"?e.last_name_field:""}}function js(n){return n?.logic?.enabled?n.logic.rules[0]?.field??ge:ge}function vt(n,e,t){const s=n.find(a=>e.includes(a.type));return s?s.id:t?n.find(a=>t.test(a.label))?.id??"":""}function Us(n,e,t){const s=r("section",{class:"atfm-form"}),a=l=>r("span",{class:`atfm-form__mark${l?"":" atfm-form__mark--off"}`,children:[r("img",{attrs:{src:e.symbol,alt:"",width:"20",height:"20"}})]}),i=l=>{R(s),s.classList.remove("is-open"),s.append(r("div",{class:"atfm-form__head",children:[a(l.length>0),r("div",{class:"atfm-form__title",children:[r("strong",{text:n.title||`Form ${n.id}`}),l.length?r("span",{class:"atfm-form__status is-on",text:`Subscribing → ${l.join(", ")}`}):r("span",{class:"atfm-form__status",text:"Not connected"})]}),$(l.length?"Edit":"Connect",()=>void o(),"secondary")]}))},o=async()=>{s.classList.add("is-open"),R(s),s.append(r("p",{class:"atfm-hint",text:"Loading the form…"}));let l;try{l=await F.getForm(n.id)}catch{R(s),s.append(r("p",{class:"atfm-hint",text:"Could not load this form."}));return}const c=l.schema.fields.filter(f=>f.type!=="page_break"),d=gt(l),h=Be(d),w=new Set(h.lists.filter(f=>e.lists.some(M=>M.id===f)));let _=js(d),u=h.email_field||vt(c,bt,/mail/i),b=h.first_name_field,g=h.last_name_field;c.some(f=>f.id===u)||(u=vt(c,bt,/mail/i));const p=c.filter(f=>Ws.includes(f.type));!d&&p.length&&(_=p[0].id);const x=(f,M)=>[{value:"",label:M},...f.map(C=>({value:C.id,label:C.label||C.id}))],I=r("div",{class:"atfm-lists"});(()=>{R(I);for(const f of e.lists){const M=r("input",{attrs:{type:"checkbox"}});M.checked=w.has(f.id),M.addEventListener("change",()=>{M.checked?w.add(f.id):w.delete(f.id)}),I.append(r("label",{class:"atfm-list",children:[M,r("span",{text:f.name})]}))}})();const H=async(f=!1)=>{const M=l.schema.actions.filter(C=>C.type!=="mailpoet");if(!f){if(!w.size){N("Pick a list","A subscription needs at least one MailPoet list.","error");return}if(!u){N("Pick the email field","MailPoet needs to know which answer is the address.","error");return}M.push({id:Hs,type:"mailpoet",enabled:!0,logic:_===ge?{enabled:!1,action:"show",match:"all",rules:[]}:{enabled:!0,action:"show",match:"all",rules:[{field:_,operator:"not_empty",value:""}]},settings:{lists:[...w],email_field:u,first_name_field:b,last_name_field:g}})}l.schema.actions=M;try{await F.updateForm(l.id,{schema:l.schema})}catch(C){N("Could not save",C instanceof Error?C.message:"","error");return}N(f?"Disconnected":"Connected to MailPoet",f?`${n.title} no longer subscribes anyone.`:`${n.title} now subscribes opted-in visitors.`),i(f?[]:e.lists.filter(C=>w.has(C.id)).map(C=>C.name))};R(s);const E=f=>r("span",{class:"atfm-form__section",text:f});s.append(r("div",{class:"atfm-form__head",children:[a(!!d),r("div",{class:"atfm-form__title",children:[r("strong",{text:n.title||`Form ${n.id}`})]}),$("Close",()=>i(d?e.lists.filter(f=>Be(d).lists.includes(f.id)).map(f=>f.name):[]),"ghost")]}),r("div",{class:"atfm-form__body",children:[E("Where they land"),y("Lists",I,"MailPoet sends its own confirmation email before anyone is truly on a list."),E("Who they are"),y("Email address",D(u,x(c,"— pick a field —"),f=>{u=f}),"The answer that carries their address."),y("First name",D(b,x(c,"— none —"),f=>{b=f})),y("Last name",D(g,x(c,"— none —"),f=>{g=f})),E("When to subscribe"),y("Subscribe",D(_,[...p.map(f=>({value:f.id,label:`Only when “${f.label||f.id}” is ticked`})),{value:ge,label:"Everyone who submits"}],f=>{_=f}),p.length?"Bind it to a box they tick. Subscribing everyone is rarely what visitors expect.":"Add a checkbox or toggle field to the form to offer a proper opt-in."),r("div",{class:"atfm-form__actions",children:d?[$("Save connection",()=>void H(),"primary"),$("Disconnect",()=>void H(!0),"danger")]:[$("Save connection",()=>void H(),"primary")]})]}))};return i([]),F.getForm(n.id).then(l=>{const c=gt(l);if(c&&!s.classList.contains("is-open")){const d=e.lists.filter(h=>Be(c).lists.includes(h.id)).map(h=>h.name);i(d)}}).catch(()=>{}),t.append(s),s}function zs(n){const e=n.logo?r("span",{class:"atfm-hero__logo",children:[r("img",{attrs:{src:n.logo,alt:"MailPoet",width:"210",height:"105"}})]}):r("span",{class:"atfm-hero__logo atfm-hero__logo--glyph",text:"📬"});return r("header",{class:"atfm-hero",children:[e,r("div",{class:"atfm-hero__words",children:[r("h1",{text:"Turn submissions into subscribers"}),r("p",{text:"Every submission is someone choosing to talk to you. Connect a form to MailPoet and the ones who opt in land on your lists by themselves — named, consented, and confirmed by MailPoet’s own double opt-in."}),n.active?r("p",{class:"atfm-hero__status is-on",text:n.lists.length===1?"Connected — 1 list ready for subscribers":`Connected — ${n.lists.length} lists ready for subscribers`}):r("p",{class:"atfm-hero__status",text:"MailPoet is not installed on this site yet"})]})]})}function Ys(){const n=(t,s,a)=>r("div",{class:"atfm-why__card",children:[r("span",{class:"atfm-why__icon",text:t}),r("strong",{text:s}),r("p",{text:a})]}),e=(t,s)=>r("a",{text:s,attrs:{href:t,target:"_blank",rel:"noreferrer"}});return[r("div",{class:"atfm-why",children:[n("✉️","Beautiful emails, made in WordPress","Design newsletters and welcome emails in a drag-and-drop editor that lives in your own admin — no external account to juggle."),n("🤝","Consent you can stand behind","Double opt-in out of the box: every address your forms send over is confirmed by the visitor before a single campaign reaches it."),n("🚀","Free to grow with","Free up to 500 subscribers, welcome automations, WooCommerce emails and open-rate stats included — a paid addon anywhere else.")]}),r("p",{class:"atfm-links",children:[e("https://www.mailpoet.com/features/","Explore MailPoet’s features ↗"),e("https://kb.mailpoet.com/","Guides & docs ↗"),e("https://www.mailpoet.com/pricing/","Plans & the free tier ↗")]})]}function Gs(){const n=(e,t)=>r("div",{class:"atfm-step",children:[r("span",{class:"atfm-step__n",text:e}),r("span",{text:t})]});return r("div",{class:"atfm-steps",children:[n("1","Pick a form below"),n("2","Choose the lists it feeds"),n("3","Bind it to an opt-in — MailPoet confirms the rest")]})}async function He(){const n=document.querySelector("[data-atfm-root]");if(!n||n.dataset.atfmMounted)return;n.dataset.atfmMounted="1",Ae(n);const e=n.querySelector("[data-atfm-bar]"),t=n.querySelector("[data-atfm-body]")??n;try{const[s,a]=await Promise.all([F.mailpoet(),F.listForms()]);if(e?.remove(),R(t),t.append(zs(s),...Ys()),!s.active){t.append(r("div",{class:"atfm-pitch",children:[r("p",{text:"MailPoet sends newsletters from right inside WordPress — no external account, free to start. Install it and every form here gets a subscribe switch: reservations feed your announcements, signups feed your schedule, enquiries feed your news."}),r("a",{class:"atfm-pitch__cta",text:"Install MailPoet — it’s free",attrs:{href:s.adminUrl,target:"_blank",rel:"noreferrer"}})]}));return}if(!s.lists.length){t.append(r("div",{class:"atfm-pitch",children:[r("p",{text:"MailPoet is here, but it has no list yet. Make one, and your forms will have somewhere to send subscribers."}),r("a",{class:"atfm-pitch__cta",text:"Open MailPoet lists",attrs:{href:s.adminUrl,target:"_blank",rel:"noreferrer"}})]}));return}t.append(Gs());const i=r("section",{class:"atfm-forms",children:[r("div",{class:"atfm-forms__head",children:[r("h2",{text:"Your forms"}),r("p",{class:"atfm-hint",text:"Connected forms wear the MailPoet mark in colour."})]})]});if(t.append(i),!a.length){i.append(r("p",{class:"atfm-hint",text:"No forms yet — make one, then come back to connect it."}));return}for(const o of a)Us(o,s,i)}catch{e&&(e.textContent="Could not reach the site — reload the window to try again.")}}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>void He()):He(),document.addEventListener("os-window-content-loaded",()=>void He());const ve={plain:"",simple:"✓",minimal:"",card:"🎉",check:"",confetti:"🎉",fireworks:"🎆",sparkles:"✨",typewriter:""};function yt(){return{style:"simple",title:"",icon:"",accent:"",intensity:"medium",showButton:!1,buttonLabel:""}}function We(n){const e={...yt(),...n??{}};return e.style in ve||(e.style="simple"),e}function Xs(){return typeof window.matchMedia=="function"&&window.matchMedia("(prefers-reduced-motion: reduce)").matches}function Zs(n,e,t){const s=We(e),a=document.createElement("div");if(a.className=`atf-confirmation atf-success atf-success--${s.style}`,a.setAttribute("role","status"),a.setAttribute("tabindex","-1"),s.style==="plain")return a.className="atf-confirmation",a.innerHTML=n,a;s.accent&&a.style.setProperty("--atf-accent",s.accent);const i=document.createElement("div");i.className="atf-success__inner",a.append(i);const o=s.icon||ve[s.style];if(s.style==="check")i.insertAdjacentHTML("beforeend",'');else if(o){const c=document.createElement("span");c.className="atf-success__icon",c.setAttribute("aria-hidden","true"),c.textContent=o,i.append(c)}if(s.title){const c=document.createElement("h2");c.className="atf-success__title",c.textContent=s.title,i.append(c)}const l=document.createElement("div");if(l.className="atf-success__message",l.innerHTML=n,i.append(l),s.style==="typewriter"&&a.setAttribute("aria-label",l.textContent??""),s.showButton){const c=document.createElement("button");c.type="button",c.className="atf-button atf-button--ghost atf-success__again",c.textContent=s.buttonLabel||"Fill it in again",c.addEventListener("click",()=>t?t():window.location.reload()),i.append(c)}return a}function Ks(n,e){const t=We(e);if(Xs())return()=>{};switch(t.style){case"confetti":return Vs(n,t);case"fireworks":return Js(t);case"sparkles":return Qs(n,t);case"typewriter":return en(n);default:return()=>{}}}function oe(n){return{low:.5,medium:1,high:1.8}[n.intensity]}function wt(){const n=document.createElement("canvas");n.className="atf-success-canvas",n.setAttribute("aria-hidden","true");const e=Math.min(window.devicePixelRatio||1,2);n.width=Math.floor(window.innerWidth*e),n.height=Math.floor(window.innerHeight*e),document.body.append(n);const t=n.getContext("2d");return t?.scale(e,e),{canvas:n,ctx:t,stop:()=>n.remove()}}const _t=["#f43f5e","#f59e0b","#10b981","#3b82f6","#8b5cf6","#ec4899","#eab308"];function Vs(n,e){const{ctx:t,stop:s}=wt();if(!t)return s(),()=>{};const a=e.accent?[e.accent,..._t]:_t,i=[],o=n.getBoundingClientRect(),l=o.left+o.width/2,c=Math.min(o.top+40,window.innerHeight-20),d=(x,I,S)=>{const H=S?Math.PI*(1.15+.7*Math.random()):0,E=S?9+Math.random()*8:0;return{x,y:I,vx:S?Math.cos(H)*E*(Math.random()<.5?1:-1):(Math.random()-.5)*1.5,vy:S?Math.sin(H)*E:1+Math.random()*2,w:6+Math.random()*5,h:8+Math.random()*7,angle:Math.random()*Math.PI,spin:(Math.random()-.5)*.3,color:a[Math.floor(Math.random()*a.length)],wobble:Math.random()*Math.PI*2}},h=Math.round(90*oe(e));for(let x=0;x{if(_>=w){window.clearInterval(u);return}i.push(d(Math.random()*window.innerWidth,-20,!1)),_++},2e3/w);let b=0;const g=performance.now(),p=x=>{t.clearRect(0,0,window.innerWidth,window.innerHeight);let I=!1;for(const S of i)S.vy+=.16,S.vx*=.99,S.vy*=.985,S.wobble+=.1,S.x+=S.vx+Math.sin(S.wobble)*.8,S.y+=S.vy,S.angle+=S.spin,S.y{window.clearInterval(u),window.cancelAnimationFrame(b),s()}}function Js(n){const{ctx:e,stop:t}=wt();if(!e)return t(),()=>{};const s=[],a=[],i=Math.round(6*oe(n))+2;let o=0;const l=()=>{s.push({x:window.innerWidth*(.15+.7*Math.random()),y:window.innerHeight,vy:-(9+Math.random()*4),targetY:window.innerHeight*(.15+.3*Math.random()),hue:Math.floor(Math.random()*360)}),o++};l();const c=window.setInterval(()=>{if(o>=i){window.clearInterval(c);return}l()},3500/i),d=u=>{const b=Math.round(70*oe(n));for(let g=0;g{e.clearRect(0,0,window.innerWidth,window.innerHeight);for(let g=s.length-1;g>=0;g--){const p=s[g];p.y+=p.vy,p.vy+=.08,e.fillStyle=`hsl(${p.hue} 90% 65%)`,e.fillRect(p.x-1.5,p.y,3,10),(p.y<=p.targetY||p.vy>=-1)&&(d(p),s.splice(g,1))}for(let g=a.length-1;g>=0;g--){const p=a[g];if(p.x+=p.vx,p.y+=p.vy,p.vy+=.045,p.vx*=.985,p.vy*=.985,p.life-=p.decay,p.life<=0){a.splice(g,1);continue}const x=p.life*(.7+.3*Math.random());e.globalAlpha=Math.max(0,x),e.fillStyle=`hsl(${p.hue} 95% ${55+25*p.life}%)`,e.beginPath(),e.arc(p.x,p.y,1.1+1.6*p.life,0,Math.PI*2),e.fill()}e.globalAlpha=1,!(o>=i&&s.length===0&&a.length===0)&&u-w<9e3?h=window.requestAnimationFrame(_):t()};return h=window.requestAnimationFrame(_),()=>{window.clearInterval(c),window.cancelAnimationFrame(h),t()}}function Qs(n,e){const t=e.icon||ve.sparkles,s=Math.round(22*oe(e)),a=[];let i=0;const o=()=>{const c=document.createElement("span");c.className="atf-success__spark",c.setAttribute("aria-hidden","true"),c.textContent=t,c.style.insetInlineStart=`${4+Math.random()*92}%`,c.style.animationDuration=`${2.6+Math.random()*1.8}s`,c.style.animationDelay=`${Math.random()*.3}s`,c.style.fontSize=`${14+Math.random()*14}px`,c.addEventListener("animationend",()=>c.remove()),n.append(c),a.push(c),i++};o();const l=window.setInterval(()=>{if(i>=s){window.clearInterval(l);return}o()},2800/s);return()=>{window.clearInterval(l),a.forEach(c=>c.remove())}}function en(n){const e=n.querySelector(".atf-success__message");if(!e)return()=>{};const t=e.innerHTML,s=e.textContent??"";if(!s)return()=>{};e.textContent="",e.classList.add("is-typing");const a=Math.min(45,3800/s.length);let i=0;const o=window.setInterval(()=>{i++,e.textContent=s.slice(0,i),i>=s.length&&(window.clearInterval(o),e.classList.remove("is-typing"),e.innerHTML=t)},a);return()=>{window.clearInterval(o),e.classList.remove("is-typing"),e.innerHTML=t}}const tn={min:-1,max:-1,sum:-1,avg:-1,round:-1,ceil:1,floor:1,abs:1,sqrt:1,pow:2},xt={"+":{precedence:1,right:!1},"-":{precedence:1,right:!1},"*":{precedence:2,right:!1},"/":{precedence:2,right:!1},"%":{precedence:2,right:!1},"^":{precedence:4,right:!0}};function sn(n,e,t=[]){if(!n||!n.trim()||n.length>2e3)return null;const s=nn(n,e,t),a=ln(s);if(!a)return null;const i=dn(a);if(!i)return null;const o=hn(i);return o===null||!Number.isFinite(o)?null:o}function nn(n,e,t){return n.replace(/\{([a-zA-Z0-9_]+)(?:\.([a-zA-Z0-9_]+))?\}/g,(s,a,i,o)=>an(n,o,s.length,a,i??"",e,t))}function an(n,e,t,s,a,i,o){const l=rn(o,s),c=Object.prototype.hasOwnProperty.call(i,s)?i[s]:null;if(a===""){const u=l?.type==="repeater"?kt(c).length:je(c,l);return St(u)}const h=(l?.fields??[]).find(u=>u.id===a)??null,w=kt(c).map(u=>je(u[a]??"",h));if(!w.length)return"0";const _=w.map(St);return on(n,e,t)?_.join(", "):_.length===1?_[0]:`( ${_.join(" + ")} )`}function rn(n,e){for(const t of n){if(t.id===e)return t;for(const s of t.fields??[])if(s.id===e)return s}return null}function on(n,e,t){const s=n.slice(e+t).replace(/^\s+/,"");if(s===""||s[0]!==")")return!1;const a=n.slice(0,e).replace(/\s+$/,""),i=/([a-zA-Z_][a-zA-Z0-9_]*)\s*\($/.exec(a);return!!i&&["sum","avg","min","max"].includes(i[1].toLowerCase())}function kt(n){if(!Array.isArray(n))return[];const e=[];for(const t of n){if(!t||typeof t!="object"||Array.isArray(t))continue;Object.values(t).some(a=>a!==""&&a!==null&&a!==void 0&&a!==!1&&!(Array.isArray(a)&&!a.length))&&e.push(t)}return e}function St(n){const e=n.toFixed(10).replace(/0+$/,"").replace(/\.$/,"");return e===""||e==="-"?"0":e}function je(n,e){if(typeof n=="boolean")return n?1:0;if(Array.isArray(n))return n.reduce((a,i)=>a+je(i,e),0);if(n==null||n==="")return 0;const t=e?.choices??[];for(const a of t)if(String(a.value)===String(n)){if(typeof a.price=="number")return a.price;if(typeof a.points=="number")return a.points;break}const s=Number(n);return Number.isFinite(s)?s:0}function ln(n){const e=[];let t=0;for(;ti.precedence||l.precedence===i.precedence&&!i.right){e.push(t.pop());continue}break}t.push(a);break}case"(":t.push(a);break;case")":{for(;t.length&&t[t.length-1].type!=="(";)e.push(t.pop());if(!t.length)return null;if(t.pop(),t.length&&t[t.length-1].type==="function"){const i=t.pop();i.arity=s.length?s.pop():1,e.push(i)}break}}for(;t.length;){const a=t.pop();if(a.type==="(")return null;e.push(a)}return e}function hn(n){const e=[];for(const t of n)switch(t.type){case"number":e.push(t.value);break;case"unary":{if(!e.length)return null;e.push(-e.pop());break}case"operator":{if(e.length<2)return null;const s=e.pop(),a=e.pop();switch(t.value){case"+":e.push(a+s);break;case"-":e.push(a-s);break;case"*":e.push(a*s);break;case"/":e.push(s===0?0:a/s);break;case"%":e.push(s===0?0:a%s);break;case"^":e.push(Math.pow(a,s));break;default:return null}break}case"function":{const s=t.arity??1;if(e.lengtht+s,0);case"avg":return e.reduce((t,s)=>t+s,0)/e.length;case"round":{const t=Math.max(-10,Math.min(10,Math.trunc(e[1]??0))),s=Math.pow(10,t);return Math.round(e[0]*s)/s}case"ceil":return Math.ceil(e[0]);case"floor":return Math.floor(e[0]);case"abs":return Math.abs(e[0]);case"sqrt":return e[0]<0?0:Math.sqrt(e[0]);case"pow":return Math.pow(e[0],e[1]??2);default:return null}}const pn=["sum","min","max","avg","round","ceil","floor","abs","sqrt","pow"],Lt=["number","range","scale","rating","total","select","multiselect","radio","checkboxes","switch","quiz"];function Ue(n,e){return n.filter(t=>t.id!==e&&Lt.includes(t.type))}function fn(n){const e=[];for(const t of n){if(t.type!=="repeater")continue;const s=t.label||t.id;e.push({label:`${s} (how many)`,insert:`{${t.id}}`});for(const a of t.fields??[])Lt.includes(a.type)&&e.push({label:`${s} · ${a.label||a.id}`,insert:`{${t.id}.${a.id}}`})}return e}function mn(n,e){const t={};Ue(n,e).forEach((s,a)=>{t[s.id]=a+1});for(const s of n){if(s.type!=="repeater"||s.id===e)continue;const a=s.fields??[],i=o=>Object.fromEntries(a.map((l,c)=>[l.id,c+1+o]));t[s.id]=[i(0),i(1)]}return t}function bn(n){const e=r("div",{class:"atfb-overlay"}),t=()=>{e.remove(),document.removeEventListener("keydown",s)},s=u=>{u.key==="Escape"&&t()},a=r("textarea",{class:"atfb-input atfb-formula__input",attrs:{rows:"3","aria-label":"Formula"}});a.value=String(n.field.formula??"");const i=r("p",{class:"atfb-formula__result",attrs:{"aria-live":"polite"}}),o=mn(n.fields,n.field.id),l=()=>{const u=a.value.trim();if(u===""){i.textContent="Empty. Reference a question below to start.",i.classList.remove("is-error");return}const b=sn(u,o,n.fields);if(b===null){i.textContent="This does not compute yet — check the braces and parentheses.",i.classList.add("is-error");return}const g=Ue(n.fields,n.field.id).map((p,x)=>`${p.label||p.id} = ${x+1}`).concat(n.fields.filter(p=>p.type==="repeater"&&p.id!==n.field.id).map(p=>`${p.label||p.id} = 2 sample rows`)).join(", ");i.textContent=`With sample answers (${g}): ${b}`,i.classList.remove("is-error")};a.addEventListener("input",l);const c=(u,b,g=0)=>r("button",{class:"atfb-formula__chip",type:"button",text:u,on:{click:()=>{if(ht(a,b),g>0){const p=(a.selectionStart??a.value.length)-g;a.setSelectionRange(p,p)}}}}),d=Ue(n.fields,n.field.id),h=fn(n.fields),w=r("div",{class:"atfb-formula__chips",children:d.length||h.length?[...d.map(u=>c(u.label||u.id,`{${u.id}}`)),...h.map(u=>c(u.label,u.insert))]:[r("p",{class:"atfb-hint",text:"No number-shaped questions yet — add a number, scale or priced choice field and it appears here."})]}),_=r("div",{class:"atfb-formula__chips",children:pn.map(u=>c(`${u}()`,`${u}()`,1))});e.append(r("div",{class:"atfb-modal atfb-formula",attrs:{role:"dialog","aria-label":"Formula editor"},children:[r("h2",{text:"Formula"}),a,i,y("Your questions",w,"Click one to reference its answer."),y("Functions",_),r("div",{class:"atfb-modal__actions",children:[$("Cancel",t),$("Save formula",()=>{n.onSave(a.value.trim()),t()},"primary")]})]})),e.addEventListener("click",u=>{u.target===e&&t()}),document.addEventListener("keydown",s),n.root.append(e),l(),a.focus(),a.setSelectionRange(a.value.length,a.value.length)}const gn="allterrain-forms/form";function vn(){return window.wp?.os?.relations??null}function Et(n){const e=n.closest("[data-window-id], .os-window");if(!e)return null;const t=e.getAttribute("data-window-id");if(t)return t;const s=e.id??"";return s?s.replace(/^wp-window-/,""):null}const yn=6e3,Ct=120;function $t(n,e){const t=vn();if(ze.set(n,e),!t?.set)return;const s=i=>{if(ye.get(n)!==a)return;const o=Et(n);if(!o){Date.now()s(i),Ct);return}try{t.set(o,e)}catch(c){At||(At=!0,console.error("[AllTerrain Forms] The shell refused a window identity.",c,e)),ye.delete(n);return}if(!e||t.get?.(o)?.id===e.id||Date.now()>=i){ye.delete(n);return}window.setTimeout(()=>s(i),Ct)},a=Symbol("atf-identity");ye.set(n,a),s(Date.now()+yn)}let At=!1;const ye=new WeakMap,ze=new Map;function wn(){for(const[n,e]of ze){if(!n.isConnected){ze.delete(n);continue}$t(n,e)}}if(typeof document<"u")for(const n of["os-window-content-loaded","os-window-opened"])document.addEventListener(n,()=>wn());function _n(n,e){return{type:gn,id:n.id,label:n.title||"Untitled form",related:[{id:`allterrain-forms/entries-${n.id}`,label:"Entries for this form",url:`${e}admin.php?page=allterrain-forms-entries&form=${n.id}`,group:"allterrain-forms",groupLabel:"Forms",icon:"dashicons-list-view"}]}}const we={letters:{label:"Letters",chars:"A-Za-zÀ-ÖØ-öø-ÿ"},numbers:{label:"Numbers",chars:"0-9"},spaces:{label:"Spaces",chars:" "},punctuation:{label:`Punctuation ( . , ! ? ' " - )`,chars:`.,!?'"()\\-:;`},symbols:{label:"Symbols ( @ # & _ / + )",chars:"@#&_/+*%="}};function xn(){return{mode:"blocks",starts:"",ends:"",contains:"",notContains:"",chars:[],minLen:"",maxLen:"",regex:"",message:"",tests:[]}}function Tt(n){const e=xn();let t;try{t=JSON.parse(n)}catch{return e}if(!t||typeof t!="object")return e;const s=t;e.mode=s.mode==="regex"?"regex":"blocks";for(const a of["starts","ends","contains","notContains","minLen","maxLen","regex","message"])typeof s[a]=="string"&&(e[a]=s[a]);return Array.isArray(s.chars)&&(e.chars=s.chars.filter(a=>typeof a=="string"&&a in we)),Array.isArray(s.tests)&&(e.tests=s.tests.filter(a=>typeof a=="string").slice(0,10)),e}function _e(n){return n.replace(/[.*+?^${}()|[\]\\/]/g,"\\$&")}function xe(n){if(n.mode==="regex")return n.regex.trim();const e=[],t=n.minLen.trim(),s=n.maxLen.trim();(t!==""||s!=="")&&e.push(`(?=.{${t===""?"0":parseInt(t,10)||0},${s===""?"":parseInt(s,10)||""}}$)`),n.contains!==""&&e.push(`(?=.*${_e(n.contains)})`),n.notContains!==""&&e.push(`(?!.*${_e(n.notContains)})`),n.starts!==""&&e.push(`(?=${_e(n.starts)})`),n.ends!==""&&e.push(`(?=.*${_e(n.ends)}$)`);const a=n.chars.map(o=>we[o]?.chars??"").join(""),i=a?`[${a}]*$`:".*$";return!e.length&&!a?"":`^${e.join("")}${i}`}function Mt(n){if(n.mode==="regex")return n.regex.trim()?`Matches the expression ${n.regex.trim()}`:"";const e=[];if(n.starts&&e.push(`starts with “${n.starts}”`),n.ends&&e.push(`ends with “${n.ends}”`),n.contains&&e.push(`contains “${n.contains}”`),n.notContains&&e.push(`never contains “${n.notContains}”`),n.chars.length){const i=n.chars.map(o=>(we[o]?.label??o).split(" (")[0].toLowerCase());e.push(`uses only ${i.join(" and ")}`)}const t=n.minLen.trim(),s=n.maxLen.trim();return t&&s?e.push(`is ${t}–${s} characters long`):t?e.push(`is at least ${t} characters long`):s&&e.push(`is at most ${s} characters long`),e.length?`The answer ${e.length>1?`${e.slice(0,-1).join(", ")}, and ${e[e.length-1]}`:e[0]}.`:""}function kn(n,e){const t=xe(n);if(t==="")return null;try{return new RegExp(t).test(e)}catch{return null}}function Sn(){return window.wp?.os?.windowManager??null}const Ft="allterrain-forms-validation";function Ln(n){const e=Sn(),t=Et(n.root);if(e?.openChild&&t){En(e,t,n);return}Cn(n)}function En(n,e,t){const s=`${Ft}-${t.field.id}`;n.getById?.(s)&&n.remove?.(s);const a={saved:!1},i=()=>{const o=n.getById?.(s);o?.close?o.close():n.remove?.(s)};n.openChild?.(e,{id:s,baseId:Ft,url:`#${s}`,title:`Custom rule — ${t.field.label||"this question"}`,icon:"dashicons-yes-alt",native:!0,width:560,height:700,minWidth:440,minHeight:480,ephemeral:!0,autofocus:".atfb-valwin__pane:not([hidden]) input, .atfb-valwin__pane:not([hidden]) textarea",render:o=>{const l=r("div",{class:"atfa atfb-valwin atfb-valwin--window"}),c=r("div",{class:"atfb-valwin__scroll"});l.append(c),It(c,t,a,i,"window");const d=c.querySelector(".atfb-modal__actions");d&&l.append(d),o.append(l)},onClose:()=>{a.saved||t.onCancel?.()}})}function Cn(n){const e=r("div",{class:"atfb-overlay"}),t={saved:!1},s=()=>{e.remove(),document.removeEventListener("keydown",a),t.saved||n.onCancel?.()},a=o=>{o.key==="Escape"&&s()},i=r("div",{class:"atfb-modal atfb-valwin",attrs:{role:"dialog","aria-label":"Custom validation rule"},children:[r("h2",{text:"Custom rule"})]});It(i,n,t,s,"overlay"),e.append(i),e.addEventListener("click",o=>{o.target===e&&s()}),document.addEventListener("keydown",a),n.root.append(e),e.querySelector(".atfb-valwin__pane:not([hidden]) input, .atfb-valwin__pane:not([hidden]) textarea")?.focus()}function It(n,e,t,s,a){const i=Tt(String(e.field.validationRecipe??""));i.mode==="blocks"&&xe(i)===""&&e.field.pattern&&(i.mode="regex",i.regex=String(e.field.pattern)),i.message||(i.message=String(e.field.messages?.invalid??""));const o=(f,M)=>{const C=r("input",{class:"atfb-input",value:i[f],placeholder:M,attrs:{type:"text"}});return C.addEventListener("input",()=>{i[f]=C.value,E()}),C},l=(f,M)=>{const C=r("input",{class:"atfb-input atfb-valwin__len",value:i[f],attrs:{type:"number",min:"0","aria-label":M}});return C.addEventListener("input",()=>{i[f]=C.value,E()}),C},c=r("div",{class:"atfb-valwin__chars",children:Object.entries(we).map(([f,M])=>O(M.label,i.chars.includes(f),C=>{i.chars=C?[...i.chars,f]:i.chars.filter(W=>W!==f),E()}))}),d=r("div",{class:"atfb-valwin__pane",children:[y("Starts with",o("starts","e.g. AT-")),y("Ends with",o("ends","e.g. -2026")),y("Must contain",o("contains","e.g. @")),y("Must not contain",o("notContains","e.g. spaces? type one")),y("Only these characters",c,"Leave every box unticked to allow anything."),y("Length",r("div",{class:"atfb-valwin__lengths",children:[r("span",{text:"between"}),l("minLen","Minimum length"),r("span",{text:"and"}),l("maxLen","Maximum length"),r("span",{text:"characters"})]}),"Leave a box empty for no limit.")]}),h=r("textarea",{class:"atfb-input atfb-valwin__regex",attrs:{rows:"2","aria-label":"Regular expression",placeholder:"^AT-[0-9]{4}$"}});h.value=i.regex,h.addEventListener("input",()=>{i.regex=h.value,E()});const w=r("div",{class:"atfb-valwin__pane",children:[y("Expression",h,"A regular expression, without slashes. Checked against the whole answer only if you anchor it with ^ and $.")]}),_=[["blocks","Easy blocks"],["regex","Expression (advanced)"]],u=f=>{i.mode=f,d.hidden=f!=="blocks",w.hidden=f!=="regex",E()},g=(()=>{if(Y("os-segmented")&&Y("os-segment")){const C=document.createElement("os-segmented");C.setAttribute("value",i.mode),C.setAttribute("label","How to write the rule"),C.classList.add("atfb-valwin__tabs");for(const[W,B]of _){const le=document.createElement("os-segment");le.setAttribute("value",W),le.textContent=B,C.append(le)}return C.addEventListener("os-pick",W=>{const B=W.detail?.value;(B==="blocks"||B==="regex")&&(C.setAttribute("value",B),u(B))}),C}const f=r("div",{class:"atfb-valwin__tabs",attrs:{role:"tablist"}}),M=()=>{f.replaceChildren(..._.map(([C,W])=>{const B=i.mode===C;return r("button",{class:`atfb-valwin__tab${B?" is-active":""}`,type:"button",text:W,attrs:{role:"tab","aria-selected":B?"true":"false"},on:{click:()=>{u(C),M()}}})}))};return M(),f})();d.hidden=i.mode!=="blocks",w.hidden=i.mode!=="regex";const p=r("input",{class:"atfb-input",value:i.message,placeholder:"That is not in the expected format.",attrs:{type:"text"}});p.addEventListener("input",()=>{i.message=p.value});const x=r("p",{class:"atfb-valwin__summary",attrs:{"aria-live":"polite"}}),I=r("div",{class:"atfb-valwin__samples"}),S=f=>{const M=r("span",{class:"atfb-valwin__verdict",attrs:{"aria-live":"polite"}}),C=r("input",{class:"atfb-input",value:f,placeholder:"Type a sample answer…",attrs:{type:"text"}});C.addEventListener("input",()=>E()),I.append(r("div",{class:"atfb-valwin__sample",children:[C,M]}))};for(const f of i.tests.length?i.tests:["","",""])S(f);const H=()=>Array.from(I.querySelectorAll("input")).map(f=>f.value),E=()=>{const f=Mt(i);x.textContent=f||"Nothing yet — fill in a block above and the rule appears here in plain words.";for(const M of Array.from(I.querySelectorAll(".atfb-valwin__sample"))){const C=M.querySelector("input"),W=M.querySelector(".atfb-valwin__verdict");if(!C||!W)continue;const B=C.value===""?null:kn(i,C.value);W.textContent=B===null?"·":B?"✓ passes":"✗ fails",W.classList.toggle("is-pass",B===!0),W.classList.toggle("is-fail",B===!1)}};n.append(r("p",{class:"atfb-hint",text:`Describe what a good answer to “${e.field.label||"this question"}” looks like — no code needed.`}),g,d,w,r("div",{class:"atfb-valwin__try",children:[r("h3",{text:"Try it out"}),x,I,$("Add another sample",()=>{S(""),E()},"ghost","plus-alt2")]}),y("When it fails, say",p,"Shown to the visitor when their answer breaks the rule. Leave empty for the default wording."),r("div",{class:"atfb-modal__actions",children:[$(a==="window"?"Close without saving":"Cancel",s),$("Save rule",()=>{const f=xe(i);i.tests=H().filter(M=>M!=="").slice(0,10),t.saved=!0,e.onSave({pattern:f,recipe:i,message:i.message.trim()}),s()},"primary")]})),E()}const $n=["Contact","Numbers & codes","Text shape","Web"],Dt=[{slug:"email",label:"An email address",group:"Contact",example:"jane@example.com",pattern:"^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$",message:"That does not look like an email address."},{slug:"phone",label:"A phone number",group:"Contact",example:"+34 612 345 678",pattern:"^(?=(?:[^0-9]*[0-9]){5,})\\+?[0-9 ().-]{5,24}$",message:"That does not look like a phone number."},{slug:"handle",label:"A username or @handle",group:"Contact",example:"@yourname",pattern:"^@?[A-Za-z0-9_]{2,30}$",message:"That does not look like a username."},{slug:"digits",label:"Numbers only",group:"Numbers & codes",example:"12345",pattern:"^[0-9]+$",message:"Numbers only, please."},{slug:"decimal",label:"A number, decimals allowed",group:"Numbers & codes",example:"3.14",pattern:"^-?[0-9]+([.,][0-9]+)?$",message:"That does not look like a number."},{slug:"price",label:"A price",group:"Numbers & codes",example:"19.99",pattern:"^[0-9]+([.,][0-9]{1,2})?$",message:"That does not look like a price."},{slug:"zip_us",label:"A ZIP code (US)",group:"Numbers & codes",example:"90210",pattern:"^[0-9]{5}(-[0-9]{4})?$",message:"That does not look like a ZIP code."},{slug:"postcode_uk",label:"A postcode (UK)",group:"Numbers & codes",example:"SW1A 1AA",pattern:"^[A-Za-z]{1,2}[0-9][A-Za-z0-9]? ?[0-9][A-Za-z]{2}$",message:"That does not look like a postcode."},{slug:"iban",label:"An IBAN",group:"Numbers & codes",example:"DE89 3704 0044 0532 0130 00",pattern:"^[A-Za-z]{2}[0-9]{2}(?: ?[A-Za-z0-9]){10,32}$",message:"That does not look like an IBAN."},{slug:"credit_card",label:"A card number",group:"Numbers & codes",example:"4242 4242 4242 4242",pattern:"^[0-9](?:[0-9 -]{9,21})?[0-9]$",message:"That does not look like a card number.",luhn:!0},{slug:"letters",label:"Letters only",group:"Text shape",example:"María López",pattern:"^[\\p{L}\\p{M} .'’-]+$",message:"Letters only, please."},{slug:"alphanumeric",label:"Letters and numbers only",group:"Text shape",example:"abc123",pattern:"^[A-Za-z0-9]+$",message:"Letters and numbers only, please."},{slug:"no_spaces",label:"One word, no spaces",group:"Text shape",example:"one-word",pattern:"^\\S+$",message:"No spaces allowed."},{slug:"url",label:"A web address",group:"Web",example:"https://example.com",pattern:"^(https?://)?([A-Za-z0-9-]+\\.)+[A-Za-z]{2,}([/?#]\\S*)?$",message:"That does not look like a web address."},{slug:"ip",label:"An IP address",group:"Web",example:"192.168.0.1",pattern:"^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$",message:"That does not look like an IP address."},{slug:"slug",label:"A URL slug",group:"Web",example:"my-page-title",pattern:"^[a-z0-9]+(-[a-z0-9]+)*$",message:"Lowercase letters, numbers and dashes only."},{slug:"hex_color",label:"A hex colour",group:"Web",example:"#3366ff",pattern:"^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$",message:"That does not look like a colour code."}];function An(n){return Dt.find(e=>e.slug===n)??null}function Ye(){return window.wp?.os??null}const qt="allterrain-forms/preview",Ge="allterrain-forms-preview";function Tn(n){const e=Ye();if(!e?.registerTitleBarButton)return()=>{};const t=()=>{try{e.registerTitleBarButton({id:qt,label:"Preview this form",icon:"dashicons-visibility",placement:"right",order:90,match:s=>{const a=s?.id??s?.config?.id??"";return a==="allterrain-forms"||a.startsWith("allterrain-forms#")},onClick:()=>void Ot(n),owner:"allterrain-forms-builder"})}catch{}};return e.ready?e.ready(t):t(),()=>{try{e.unregisterTitleBarButton?.(qt)}catch{}}}async function Ot(n){n.isDirty()&&await n.save();const e=n.current();e&&Nt(e.id,e.title,e.previewUrl)}function Nt(n,e,t){const s=Ye();if(!s?.windowManager?.open){window.open(t,"_blank","noopener");return}s.windowManager.open({id:`${Ge}-${n}`,baseId:Ge,url:t,title:`Preview: ${e}`,icon:"dashicons-visibility"})}function Mn(n,e,t){if(!Ye()?.windowManager?.open||!document.querySelector(`[data-window-id="${Ge}-${n}"]`))return;const i=t.includes("?")?"&":"?";Nt(n,e,`${t}${i}alltfo_r=${Date.now()}`)}const se="allterrain-forms/field",Fn=["openstation/file","desktop-mode/file","openstation/attachment"],Rt="allterrain-forms/logic-map-v2";function J(n,e){return n.dataset.atfbBind=e,n}const Pt={level:{key:"level",label:"Heading level",control:"select",hint:"Headings should step down one at a time.",options:[{value:"2",label:"Heading 2"},{value:"3",label:"Heading 3"},{value:"4",label:"Heading 4"},{value:"5",label:"Heading 5"},{value:"6",label:"Heading 6"}]},content:{key:"content",label:"HTML",control:"textarea",hint:"Shown as written. Scripts are stripped when the form is saved."},consenttext:{key:"consentText",label:"What they are agreeing to",control:"textarea",hint:"Shown beside the tick box. Links are allowed."},height:{key:"height",label:"Height",control:"number",hint:"In pixels."},columns:{key:"columns",label:"Columns",control:"number",hint:"How many pictures sit side by side."},multiple:{key:"multiple",label:"Let them choose more than one",control:"checkbox"},inline:{key:"inline",label:"Lay the options out in a row",control:"checkbox"},other:{key:"other",label:"Offer an “Other” box",control:"checkbox",hint:"Adds a final option with a box to type in."},filetypes:{key:"filetypes",label:"Accepted file types",control:"commas",hint:"Extensions, separated by commas. Empty accepts anything the site allows."},maxsize:{key:"maxsize",label:"Largest file",control:"number",hint:"In megabytes."},maxfiles:{key:"maxfiles",label:"How many files",control:"number"},minrows:{key:"minRows",label:"Fewest rows",control:"number",also:{key:"maxRows",label:"Most rows"}},itemlabel:{key:"itemLabel",label:"What is one row called?",control:"text",hint:"Names each card — “Attendee 1”, “Attendee 2” — and the Remove button. Also editable on the card itself."},endlabels:{key:"minLabel",label:"Label at the low end",control:"text",also:{key:"maxLabel",label:"Label at the high end"}},points:{key:"points",label:"Points if correct",control:"number"},minchoices:{key:"minChoices",label:"Fewest they may pick",control:"number",also:{key:"maxChoices",label:"Most they may pick"}}},In={label:"the canvas edits it in place, and the inspector mirrors it",placeholder:"edited by typing into the control on the canvas",hint:"edited under the control on the canvas",nextlabel:"edited on the page break’s own Next button",prevlabel:"edited on the page break’s own Back button",addlabel:"edited on the repeater’s own Add button",choices:"the choices editor, and the option list on the canvas",required:"the toggle on the card",width:"its own row",css:"its own row",prefill:"its own section",logic:"the conditional logic section",formula:"its own row, with the currency that goes with it",currency:"rendered with the formula",correct:"the choices editor marks the right answer",default:"its own row, typed to match what the field stores",rows:"a row count on a textarea; the statement list on a Likert matrix",parts:"a tick box per part, listed by the server",min:"the validation section, paired with max",max:"the validation section, paired with min",step:"the validation section",minlength:"the validation section, paired with maxlength",maxlength:"the validation section, paired with minlength",mindate:"the validation section, paired with maxdate",maxdate:"the validation section, paired with mindate",mintime:"the validation section, paired with maxtime",maxtime:"the validation section, paired with mintime",pattern:"the validation section",unique:"the validation section",maxchoices:"rendered with minchoices",maxrows:"rendered with minrows"};function Bt(n,e,t){const s=n[e.key],a=i=>t(e.key,i);if(e.control==="checkbox")return O(e.label,!!s,a);if(e.control==="commas"){const i=Array.isArray(s)?s:[];return y(e.label,T(i.join(", "),o=>{a(o.split(",").map(l=>l.trim().replace(/^\./,"").toLowerCase()).filter(Boolean))}),e.hint)}return e.control==="select"?y(e.label,D(String(s??""),e.options??[],a),e.hint):e.control==="textarea"?y(e.label,pe(String(s??""),a),e.hint):e.control==="number"?y(e.label,G(String(s??""),a),e.hint):y(e.label,J(T(String(s??""),a),e.key),e.hint)}function Ht(n,e){const t=new Set(n.map(a=>a.key).filter(Boolean));let s=n.length+1;return e.split(` +`).map(a=>a.trim()).filter(Boolean).map((a,i)=>{const o=n[i]?.key;if(o)return{key:o,label:a};for(;t.has(`r${s}`);)s+=1;return t.add(`r${s}`),{key:`r${s}`,label:a}})}function Wt(n,e,t){const s=n.findIndex(i=>i.id===e);if(s<0)return null;const a=Math.max(0,Math.min(t,n.length-1));return a===s?null:{from:s,to:a}}const Xe=[{value:"user:email",label:"Their email address",group:"About the person filling it in",tag:"{user:email}"},{value:"user:display_name",label:"Their name",group:"About the person filling it in",tag:"{user:display_name}"},{value:"user:first_name",label:"Their first name",group:"About the person filling it in"},{value:"user:last_name",label:"Their last name",group:"About the person filling it in"},{value:"user:login",label:"Their username",group:"About the person filling it in"},{value:"date:today",label:"Today’s date",group:"The date and time",tag:"{date}"},{value:"date:now",label:"The time right now",group:"The date and time",tag:"{time}"},{value:"site",label:"This site’s name",group:"About this site",tag:"{site}"},{value:"site:url",label:"This site’s address",group:"About this site",tag:"{site:url}"},{value:"site:admin_email",label:"The site administrator’s email",group:"About this site",tag:"{admin_email}"}],Dn=["About the person filling it in","The date and time","About this site"],Ze=(n,e)=>j?.i18n?.[n]??e;class jt{constructor(e){this.config=null,this.themes=[],this.forms=[],this.form=null,this.schema=null,this.selected=null,this.tab="build",this.logicMap=null,this.canvasTheme=r("style"),this.canvasThemeSignature="",this.openSections=new Map,this.logicMapOn=is(Rt)!=="off",this.dirty=!1,this.editGeneration=0,this.saveInFlight=!1,this.queuedSave=null,this.teardowns=[],this.canvasTarget=null,this.history=[],this.historyAt=-1,this.autosave=$e(()=>{this.save(!0)},2500),this.renderInspector=as(()=>{if(R(this.inspector),!this.schema||(this.root.classList.toggle("atfb--build-only-panes",this.tab!=="build"),this.tab!=="build"))return;const t=this.selected?this.locateField(this.selected):void 0,s=t?.field,a=t?.parent??null;if(this.root.classList.toggle("atfb--has-selection",!!s),!s){this.inspector.append(r("div",{class:"atfb-placeholder",children:[r("p",{text:"Select a field to change it."}),r("p",{class:"atfb-hint",text:"Drag a field from the palette, or press one to add it to the end."})]}));return}const i=this.config?.fieldTypes.find(c=>c.type===s.type),o=i?.supports??[],l=(c,d)=>{const h=this.liveField(s.id);h&&(h[c]=d,this.markDirty(),this.renderCanvas())};this.inspector.append(r("h3",{class:"atfb-inspector__title",text:i?.label??s.type})),a?this.inspector.append(r("p",{class:"atfb-hint atfb-inspector__crumb",text:`Inside ${a.label||"a repeater"} — the visitor answers this once per ${String(a.itemLabel??"")||"row"}.`}),r("p",{class:"atfb-hint",text:`Formulas aggregate it as {${a.id}.${s.id}} — e.g. sum( {${a.id}.${s.id}} ).`})):this.inspector.append(r("p",{class:"atfb-hint",text:`Reference this field as {field:${s.id}}`})),o.includes("label")&&this.inspector.append(y("Label",J(T(s.label,c=>l("label",c)),"label"))),o.includes("placeholder")&&this.inspector.append(y("Placeholder",J(T(s.placeholder,c=>l("placeholder",c)),"placeholder"))),o.includes("hint")&&this.inspector.append(y("Hint",J(T(s.hint,c=>l("hint",c)),"hint"),"Shown under the field, and read out with it.")),o.includes("nextlabel")&&this.inspector.append(y("Next button",J(T(String(s.nextLabel??""),c=>l("nextLabel",c)),"nextLabel"),"Leave empty for “Next”.")),o.includes("prevlabel")&&this.inspector.append(y("Back button",J(T(String(s.prevLabel??""),c=>l("prevLabel",c)),"prevLabel"),"Leave empty for “Back”.")),o.includes("addlabel")&&this.inspector.append(y("Add button",J(T(String(s.addLabel??""),c=>l("addLabel",c)),"addLabel"),"Leave empty for “Add another”.")),o.includes("required")&&this.inspector.append(O("Required",s.required,c=>l("required",c))),o.includes("width")&&this.inspector.append(y("Width",D(s.width,[{value:"full",label:"Full width"},{value:"two-thirds",label:"Two thirds"},{value:"half",label:"Half"},{value:"third",label:"One third"},{value:"quarter",label:"One quarter"}],c=>l("width",c)))),i?.choices&&this.inspector.append(this.renderChoicesEditor(s,l)),this.renderTypeSettings(s,i,o,l),(s.type==="total"||o.includes("formula"))&&this.inspector.append(y("Formula",r("div",{class:"atfb-formula__row",children:[T(String(s.formula??""),c=>l("formula",c)),$("Formula editor",()=>bn({root:this.root,fields:this.schema?.fields??[],field:s,onSave:c=>{l("formula",c),this.renderInspector()}}))]}),"Reference answers with braces — {f1} * {f2} + 10 — or open the editor and click them in."),y("Currency symbol",T(String(s.currency??""),c=>l("currency",c)))),this.inspector.append(this.renderValidationSection(s,o,l)),a||(this.inspector.append(this.renderLogicSection(s)),o.includes("prefill")&&this.inspector.append(this.prefillControl(s,l))),o.includes("css")&&this.inspector.append(y("CSS class",T(s.cssClass,c=>l("cssClass",c))))}),this.root=e,this.bar=e.querySelector("[data-atfb-bar]")??r("div"),this.palette=e.querySelector("[data-atfb-palette]")??r("div"),this.canvas=e.querySelector("[data-atfb-canvas]")??r("div"),this.inspector=e.querySelector("[data-atfb-inspector]")??r("div"),this.canvas.addEventListener("click",t=>{const s=t.target;if(!(this.tab!=="build"||!this.selected||s.closest("[data-atfb-card], button, os-button, a, input, textarea, select, os-select, label, [contenteditable]"))){this.selected=null;for(const a of this.canvas.querySelectorAll(".atfb-card.is-selected"))a.classList.remove("is-selected");this.renderInspector()}})}async start(){try{const[s,a,i]=await Promise.all([F.config(),F.listThemes(),F.listForms()]);this.config=s,this.themes=a,this.forms=i}catch(s){this.fail(s);return}this.teardowns.push(Jt([se])),this.teardowns.push(Tn({current:()=>this.form?{id:this.form.id,title:this.form.title,previewUrl:this.form.previewUrl}:null,isDirty:()=>this.dirty,save:()=>this.save(!0)}));const e=s=>{this.dirty&&(s.preventDefault(),s.returnValue="")};window.addEventListener("beforeunload",e),this.teardowns.push(()=>window.removeEventListener("beforeunload",e));const t=s=>{!(s.metaKey||s.ctrlKey)||s.key.toLowerCase()!=="z"||s.target?.closest("input, textarea, [contenteditable]")||(s.preventDefault(),this.travel(s.shiftKey?1:-1))};if(this.root.addEventListener("keydown",t),this.teardowns.push(()=>this.root.removeEventListener("keydown",t)),this.renderBar(),this.renderPalette(),this.forms.length){const s=ms("builder");await this.open(s&&this.forms.some(a=>a.id===s)?s:this.forms[0].id)}else this.renderFormsList()}destroy(){this.canvasTarget?.(),this.canvasTarget=null,this.teardowns.forEach(e=>e()),this.teardowns=[]}fail(e){R(this.bar),this.bar.append(r("p",{class:"atfb-error",text:e instanceof Error?e.message:"Something went wrong loading your forms."}))}renderBar(){R(this.bar);const e=D(String(this.form?.id??""),[...this.forms.map(a=>({value:String(a.id),label:a.title||"(untitled)"}))],a=>void this.open(Number(a)));e.setAttribute("aria-label","Choose a form");const t=r("input",{class:"atfb-title",type:"text",value:this.form?.title??"",placeholder:"Untitled form",attrs:{"aria-label":"Form title"},on:{input:a=>{this.form&&(this.form.title=a.target.value,this.markDirty())}}}),s=r("div",{class:"atfb-tabs",attrs:{role:"tablist"},children:[["build","Build"],["theme","Theme"],["settings","Settings"],["notify","Notifications"],["confirm","Confirmations"]].map(([a,i])=>r("button",{class:`atfb-tab${this.tab===a?" is-active":""}`,type:"button",text:i,attrs:{role:"tab","aria-selected":this.tab===a},on:{click:()=>{this.tab=a,this.renderBar(),this.renderInspector(),this.renderCanvas()}}}))});this.bar.append(r("div",{class:"atfb-bar__left",children:[this.forms.length>1?e:null,t]}),s,r("div",{class:"atfb-bar__right",children:[this.historyButton("undo","Undo",-1),this.historyButton("redo","Redo",1),$("New",()=>void this.showTemplates(),"secondary","plus-alt2"),$("Export",()=>void this.exportForm(),"secondary","download"),$("Import",()=>void this.importForm(),"secondary","upload"),$("Preview",()=>void this.preview(),"secondary","visibility"),$("Entries",()=>this.openEntries(),"secondary","list-view"),this.logicMapButton(),this.saveButton()]}))}liveField(e){return this.locateField(e)?.field}locateField(e){const t=this.schema?.fields??[];for(let s=0;sthis.rebindCanvas(),{once:!0});return}this.renderCanvas()}snapshot(){if(!this.schema)return;const e=JSON.stringify(this.schema);this.history[this.historyAt]!==e&&(this.history=this.history.slice(0,this.historyAt+1),this.history.push(e),this.history.length>60&&this.history.shift(),this.historyAt=this.history.length-1,this.syncHistoryButtons())}travel(e){const t=this.historyAt+e;!this.schema||t<0||t>=this.history.length||(this.historyAt=t,this.schema=JSON.parse(this.history[t]),this.schema.fields.some(s=>s.id===this.selected)||(this.selected=null),this.markDirty(),this.renderBar(),this.renderCanvas(),this.renderInspector())}historyButton(e,t,s){const a=$(t,()=>this.travel(s),"secondary",e);return a.setAttribute("data-atfb-history",String(s)),a.disabled=!this.canTravel(s),a}canTravel(e){const t=this.historyAt+e;return t>=0&&tURL.revokeObjectURL(s),1e3)}async importForm(){const e=r("input",{type:"file",attrs:{accept:"application/json,.json"}});e.addEventListener("change",async()=>{const t=e.files?.[0];if(t)try{const s=JSON.parse(await t.text());if(!s.schema)throw new Error("That file does not contain a form.");const a=await F.createForm({title:s.title??t.name.replace(/\.json$/i,""),schema:s.schema});this.forms.unshift({id:a.id,title:a.title,status:a.status,modified:a.modified,fields:a.schema.fields.length,theme:a.schema.settings.theme,entries:0,unread:0,views:0,submissions:0,shortcode:a.shortcode}),this.form=a,this.schema=a.schema,this.selected=null,this.dirty=!1,this.history=[],this.historyAt=-1,this.snapshot(),this.renderBar(),this.renderCanvas(),this.renderInspector(),N("Form imported",a.title)}catch(s){N("Could not import that file",s instanceof Error?s.message:"","error")}}),e.click()}saveButton(){const e=$("Save",()=>void this.save(),"primary");return e.disabled=!this.dirty,e.setAttribute("data-atfb-save",""),e}markDirty(){this.dirty=!0,this.editGeneration+=1;const e=this.bar.querySelector("[data-atfb-save]");e&&(e.disabled=!1),this.autosave()}async save(e=!1){if(!this.form||!this.schema)return;if(this.saveInFlight){this.queuedSave={silent:e&&(this.queuedSave?.silent??!0)};return}this.saveInFlight=!0;const t=this.editGeneration;try{const s=await F.updateForm(this.form.id,{title:this.form.title,schema:this.schema});if(t===this.editGeneration){this.form=s,this.schema=s.schema,this.dirty=!1,this.rebindCanvas();const i=this.bar.querySelector("[data-atfb-save]");i&&(i.disabled=!0)}Ms(s.id);const a=this.forms.find(i=>i.id===s.id);a&&(a.title=s.title),Mn(s.id,s.title,s.previewUrl),e||N("Form saved",s.title)}catch(s){N(Ze("saveFailed","Could not save"),s instanceof Error?s.message:"","error")}finally{this.saveInFlight=!1;const s=this.queuedSave;this.queuedSave=null,s&&this.save(s.silent)}}async openFormById(e){await this.open(e)}async open(e){if(!(this.dirty&&!await ie("You have unsaved changes. Discard them?"))){try{this.form=await F.getForm(e),this.schema=this.form.schema,this.selected=null,this.dirty=!1,this.history=[],this.historyAt=-1,this.snapshot()}catch(t){this.fail(t);return}this.renderBar(),this.renderCanvas(),this.renderInspector(),this.announceIdentity()}}announceIdentity(){this.form&&$t(this.root,_n(this.form,j?.adminUrl??""))}async showTemplates(){if(!this.config)return;const e=r("div",{class:"atfb-overlay"}),t=i=>{i.key==="Escape"&&s()},s=()=>{e.remove(),document.removeEventListener("keydown",t)},a=r("div",{class:"atfb-templates",children:this.config.templates.map(i=>r("button",{class:"atfb-template",type:"button",on:{click:async()=>{s();try{const o=await F.createForm({template:i.slug});this.forms.unshift({id:o.id,title:o.title,status:o.status,modified:o.modified,fields:o.schema.fields.length,theme:o.schema.settings.theme,entries:0,unread:0,views:0,submissions:0,shortcode:o.shortcode}),this.form=o,this.schema=o.schema,this.selected=null,this.dirty=!1,this.history=[],this.historyAt=-1,this.snapshot(),this.renderBar(),this.renderCanvas(),this.renderInspector(),this.announceIdentity()}catch(o){N("Could not create the form",o instanceof Error?o.message:"","error")}}},children:[z(i.icon),r("strong",{text:i.label}),r("span",{text:i.description})]}))});e.append(r("div",{class:"atfb-modal",attrs:{role:"dialog","aria-label":"Start a new form"},children:[r("h2",{text:"Start a new form"}),a,this.archivedFormsSection(s),r("div",{class:"atfb-modal__actions",children:[$("Cancel",s)]})]})),e.addEventListener("click",i=>{i.target===e&&s()}),document.addEventListener("keydown",t),this.root.append(e),a.querySelector("button")?.focus()}archivedFormsSection(e){const t=r("div",{class:"atfb-archived"});return F.listArchivedForms().then(s=>{s.length&&t.append(r("h3",{class:"atfb-archived__title",text:"Or bring one back from the archive"}),...s.map(a=>r("div",{class:"atfb-archived__row",children:[r("div",{class:"atfb-archived__meta",children:[r("strong",{text:a.title||"(untitled)"}),r("span",{class:"atfb-hint",text:`${a.entries} ${a.entries===1?"entry":"entries"} · ${a.submissions} submissions · ${a.views} views`})]}),$("Restore",async()=>{try{const i=await F.unarchiveForm(a.id);e(),this.forms.unshift(i),N("Form restored",`${i.title||"(untitled)"} is back, with its entries and stats.`),await this.open(i.id)}catch(i){N("Could not restore the form",i instanceof Error?i.message:"","error")}},"secondary","undo")]})))}).catch(()=>{}),t}renderFormsList(){R(this.canvas),this.canvas.append(r("div",{class:"atfb-empty",children:[r("h2",{text:"No forms yet"}),r("p",{text:"Start from a template, or build one from nothing."}),$("New form",()=>void this.showTemplates(),"primary","plus-alt2")]}))}openEntries(){const e=window.wp?.os;if(e?.openWindow){e.openWindow("allterrain-forms-entries");return}window.location.assign(`${j?.adminUrl??""}admin.php?page=allterrain-forms-entries`)}renderPalette(){if(!this.config)return;R(this.palette);const e=new Map;for(const s of this.config.fieldTypes){const a=e.get(s.group)??[];a.push(s),e.set(s.group,a)}const t=r("input",{class:"atfb-input atfb-palette__search",type:"search",placeholder:"Search fields",attrs:{"aria-label":"Search field types"},on:{input:s=>{const a=s.target.value.toLowerCase().trim();this.palette.querySelectorAll(".atfb-chip").forEach(i=>{const o=(i.textContent??"").toLowerCase();i.hidden=a!==""&&!o.includes(a)}),this.palette.querySelectorAll(".atfb-group").forEach(i=>{const o=Array.from(i.querySelectorAll(".atfb-chip")).some(l=>!l.hidden);i.hidden=!o})}}});this.palette.append(t);for(const[s,a]of Object.entries(this.config.groups)){const i=e.get(s);i?.length&&this.palette.append(r("div",{class:"atfb-group",children:[r("h3",{class:"atfb-group__title",text:a}),r("div",{class:"atfb-group__items",children:i.map(o=>this.paletteChip(o))})]}))}}paletteChip(e){const t=r("button",{class:"atfb-chip",type:"button",title:e.description,attrs:{"data-atf-type":e.type},children:[z(e.icon),r("span",{text:e.label})]});return t.addEventListener("pointerdown",s=>{const a=r("div",{class:"atfb-chip atfb-chip--ghost",children:[z(e.icon),r("span",{text:e.label})]});Z().start({payload:Ee(se,t,{fieldType:e.type,isNew:!0},s,a),origin:s,onClickOnly:()=>this.addField(e.type)})}),t.addEventListener("click",s=>{Z().recentlyEndedDrag()&&s.preventDefault()}),t}renderCanvas(){if(R(this.canvas),!this.schema||!this.form){this.renderFormsList();return}if(this.tab!=="build"){this.canvas.append(this.renderTabCanvas());return}const e=r("div",{class:"atfb-canvas__list",attrs:{"data-atfb-list":""}});this.schema.fields.length||e.append(r("div",{class:"atfb-placeholder",text:Ze("emptyCanvas","Drag a field from the left to begin.")})),this.schema.fields.forEach((s,a)=>{e.append(this.renderFieldCard(s,a))});const t=r("div",{class:"atfb-canvas__inner",children:[r("p",{class:"atfb-shortcode",text:this.form.shortcode,title:"Paste this anywhere to place the form"}),e]});this.canvas.append(t),this.registerCanvasTarget(e),this.paintLogicMap(t),this.paintCanvasTheme()}prefillControl(e,t){const s=e.prefill.startsWith("query:"),a=Xe.some(_=>_.value===e.prefill),i=s?"query":a&&e.prefill||(e.prefill?"custom":""),o=r("div",{class:"atfb-prefill__detail"}),l=r("p",{class:"atfb-row__hint atfb-prefill__preview"}),c=_=>{if(o.replaceChildren(),l.replaceChildren(),_==="query"){const u=e.prefill.startsWith("query:")?e.prefill.slice(6):"";o.append(T(u,b=>{const g=b.trim();t("prefill",g?`query:${g}`:""),d(`query:${g}`)},"utm_source"),r("p",{class:"atfb-row__hint",text:"The name of the parameter in the link people arrive on."}))}_==="custom"&&o.append(T(e.prefill,u=>{t("prefill",u),d(u)},"myplugin:something"),r("p",{class:"atfb-row__hint",text:"For a source another plugin has added through alltfo_resolve_prefill."})),d(e.prefill)},d=_=>{if(l.replaceChildren(),!_)return;if(_.startsWith("query:")){const b=_.slice(6);b&&(l.textContent=`A visit to …/your-page/?${b}=abc opens the form with “abc” in it.`);return}const u=Xe.find(b=>b.value===_)?.tag;u&&qe(this.form?.id??0).then(b=>{for(const g of b)for(const p of g.items)if(p.tag===u){const x=_.startsWith("user:");if(!p.sample){l.textContent="Empty unless the visitor is signed in.";return}l.textContent=x?`Opens with “${p.sample}” for you — empty for a visitor who is not signed in.`:`Opens with “${p.sample}”.`;return}})};c(i);const h=r("select",{class:"atfb-input atfb-select",on:{change:_=>{const u=_.target.value;t("prefill",u==="query"||u==="custom"?"":u),c(u)}}});h.append(r("option",{value:"",text:"Nothing — leave it empty",attrs:{selected:i===""}}));for(const _ of Dn){const u=document.createElement("optgroup");u.label=_;for(const b of Xe.filter(g=>g.group===_))u.append(r("option",{value:b.value,text:b.label,attrs:{selected:b.value===i}}));h.append(u)}const w=document.createElement("optgroup");return w.label="From the link they arrived on",w.append(r("option",{value:"query",text:"A parameter in the web address",attrs:{selected:i==="query"}})),h.append(w,r("option",{value:"custom",text:"Something else (advanced)",attrs:{selected:i==="custom"}})),y("Pre-fill this with",r("div",{class:"atfb-prefill",children:[h,o,l]}),"What the box already contains when the form opens. They can still change it.")}renderCondition(e,t){const s=t.some(i=>i.kind==="field"&&i.missing),a=r("span",{class:`atfb-cond${s?" is-broken":""}`,attrs:{"aria-label":rt(t)},children:[z("randomize"),...t.map(i=>this.renderConditionToken(e,i))]});return a.addEventListener("pointerdown",i=>i.stopPropagation()),a.addEventListener("click",i=>i.stopPropagation()),a.addEventListener("keydown",i=>i.stopPropagation()),a}editCondition(e,t,s=!0,a=""){const i=this.liveField(e)?.logic;if(i){if(t(i),this.markDirty(),!s){this.logicMap?.setEdges(me(this.schema?.fields??[]));return}this.renderCanvas(),this.renderInspector(),a&&window.requestAnimationFrame(()=>{this.canvas.querySelector(`[data-cond="${CSS.escape(a)}"]`)?.focus()})}}condSelect(e,t,s,a,i){if(Y("os-select")&&Y("os-option")){const o=document.createElement("os-select");o.setAttribute("value",e),o.setAttribute("aria-label",a),o.setAttribute("data-cond",s),o.className="atfb-cond__control",o.title=a;for(const l of t){const c=document.createElement("os-option");c.setAttribute("value",l.value),c.textContent=l.label,o.append(c)}return o.addEventListener("os-pick",l=>{i(String(l.detail?.value??""))}),o}return r("select",{class:"atfb-cond__control atfb-cond__control--native",title:a,attrs:{"aria-label":a,"data-cond":s},on:{change:o=>i(o.target.value)},children:t.map(o=>r("option",{value:o.value,text:o.label,attrs:{selected:o.value===e}}))})}renderConditionToken(e,t){if(t.kind==="field"&&!t.missing)return r("button",{class:"atfb-cond__chip atfb-cond__chip--field",type:"button",text:t.text,title:"Go to this question",on:{click:a=>{a.stopPropagation(),this.selectField(t.fieldId),this.canvas.querySelector(`[data-atfb-card="${CSS.escape(t.fieldId)}"]`)?.scrollIntoView({block:"nearest",behavior:"smooth"})}}});if(t.kind==="verb")return r("button",{class:"atfb-cond__verb",type:"button",text:t.text,title:"Switch between showing and hiding this field when the condition matches.",attrs:{"data-cond":`${e.id}:verb`},on:{click:()=>this.editCondition(e.id,s=>{s.action=s.action==="hide"?"show":"hide"},!0,`${e.id}:verb`)}});if(t.kind==="join")return r("button",{class:"atfb-cond__join",type:"button",text:t.text,title:"Switch between needing every rule (and) or any one of them (or).",attrs:{"data-cond":`${e.id}:join`},on:{click:()=>this.editCondition(e.id,s=>{s.match=s.match==="all"?"any":"all"},!0,`${e.id}:join`)}});if(t.kind==="operator"){const s=`${e.id}:op:${t.ruleIndex}`;return this.condSelect(t.operator,Object.entries(Te).map(([a,i])=>({value:a,label:i})),s,"How the answer is compared",a=>this.editCondition(e.id,i=>{const o=i.rules[t.ruleIndex];o&&(o.operator=a)},!0,s))}return t.kind==="value"?this.renderConditionValue(e,t):r("span",{class:"atfb-cond__chip atfb-cond__chip--missing",text:t.text})}renderConditionValue(e,t){const s=`${e.id}:value:${t.ruleIndex}`,a=this.schema?.fields.find(c=>c.id===t.sourceId),i=(c,d)=>this.editCondition(e.id,h=>{const w=h.rules[t.ruleIndex];w&&(w.value=c)},d,s);if(a?.choices?.length){const c=a.choices.map(d=>({value:d.value,label:d.label||d.value}));return t.raw!==""&&!a.choices.some(d=>d.value===t.raw)&&c.unshift({value:t.raw,label:t.text}),this.condSelect(t.raw,c,s,"The answer that triggers this",d=>i(d,!0))}const o=["number","range","scale","rating","total"].includes(a?.type??""),l=r("input",{class:"atfb-cond__chip atfb-cond__chip--value atfb-cond__value",value:t.raw,title:"The answer that triggers this. Edit it here.",attrs:{type:"text","aria-label":"The answer that triggers this","data-cond":s,inputmode:o?"decimal":void 0,size:String(Math.max(2,Math.min(24,t.raw.length||2)))}});return l.addEventListener("input",()=>{l.size=Math.max(2,Math.min(24,l.value.length||2)),i(l.value,!1)}),l.addEventListener("change",()=>i(l.value,!0)),l.addEventListener("keydown",c=>{c.key==="Enter"&&(c.preventDefault(),i(l.value,!0))}),l}section(e,t,s,a=!1){const i=r("details",{class:"atfb-section",attrs:{open:this.openSections.get(e)??a},children:[r("summary",{text:t}),...s]});return i.addEventListener("toggle",()=>this.openSections.set(e,i.open)),i}logicMapButton(){const e=me(this.schema?.fields??[]).length>0,t=$(this.logicMapOn?"Hide logic":"Show logic",()=>{this.logicMapOn=!this.logicMapOn,rs(Rt,this.logicMapOn?"on":"off"),this.renderBar(),this.renderCanvas()},this.logicMapOn?"primary":"secondary","randomize");return t.title="Draw a line from each question to the ones it decides.",t.hidden=!e,t}paintLogicMap(e){this.logicMap?.destroy(),this.logicMap=null;const t=this.schema?.fields??[],s=me(t);if(!s.length||!this.logicMapOn)return;e.classList.add("has-logicmap");const a=new ws(e);a.setEdges(s),a.highlight(this.selected??""),this.logicMap=a,e.addEventListener("pointerover",i=>{const o=i.target.closest("[data-atfb-card]");a.highlight(o?.dataset.atfbCard??this.selected??"")}),e.addEventListener("pointerleave",()=>a.highlight(this.selected??""))}renderFieldCard(e,t){const s=this.config?.fieldTypes.find(d=>d.type===e.type),a=this.selected===e.id,i=this.schema?.fields??[],o=gs(e,i),l=ys(i).get(e.id)??0,c=r("div",{class:`atfb-card${a?" is-selected":""}`,attrs:{"data-atfb-card":e.id,"data-index":t,tabindex:"0",role:"button","aria-pressed":a,"aria-label":`${e.label||s?.label||e.type}, ${t+1} of ${this.schema?.fields.length??0}`},children:[r("div",{class:"atfb-card__bar",children:[r("div",{class:"atfb-card__grip",attrs:{"aria-hidden":"true"},children:[z("menu")]}),r("div",{class:"atfb-card__head",children:[z(s?.icon??"dashicons-forms"),r("span",{class:"atfb-card__type",text:s?.label??e.type}),this.requiredToggle(e),l?r("span",{class:"atfb-badge atfb-badge--controls",text:l===1?"controls 1 field":`controls ${l} fields`,title:"Other questions appear or disappear based on this answer."}):null]}),r("div",{class:"atfb-card__actions",children:[this.cardAction("admin-page","Duplicate",()=>this.duplicateField(e.id)),this.cardAction("trash","Delete",()=>void this.deleteField(e.id))]})]}),r("div",{class:"atfb-card__body",children:[dt(e,s,{edit:d=>{const h=this.liveField(e.id);if(!h)return;d(h),this.markDirty(),this.syncInspector(h);const w=this.selected?this.locateField(this.selected):void 0;w?.parent&&w.parent.id===h.id&&this.syncInspector(w.field)},restructure:d=>{const h=this.liveField(e.id);h&&(this.snapshot(),d(h),this.markDirty(),this.renderCanvas(),this.renderInspector())},types:d=>this.config?.fieldTypes.find(h=>h.type===d),selectedId:this.selected}),o.length?this.renderCondition(e,o):null]})]});return c.addEventListener("click",d=>{d.target.closest(".atfb-card__actions")||Z().recentlyEndedDrag()||this.selectField(e.id)}),c.addEventListener("keydown",d=>{if(d.key==="Enter"||d.key===" "){d.preventDefault(),this.selectField(e.id);return}d.altKey&&(d.key==="ArrowUp"||d.key==="ArrowDown")&&(d.preventDefault(),this.moveField(e.id,d.key==="ArrowUp"?t-1:t+1),window.requestAnimationFrame(()=>{this.canvas.querySelector(`[data-atfb-card="${CSS.escape(e.id)}"]`)?.focus()})),(d.key==="Delete"||d.key==="Backspace")&&(d.preventDefault(),this.deleteField(e.id))}),c.addEventListener("pointerdown",d=>{d.target.closest(".atfb-card__actions")||Z().start({payload:Ee(se,c,{fieldId:e.id,field:e,isNew:!1},d),origin:d})}),c}requiredToggle(e){return r("button",{class:`atfb-req${e.required?" is-on":""}`,type:"button",text:e.required?"Required":"Optional",title:e.required?"This must be answered. Click to make it optional.":"Click to make this required.",attrs:{"aria-pressed":e.required?"true":"false"},on:{pointerdown:s=>s.stopPropagation(),click:s=>{s.stopPropagation(),e.required=!e.required,this.markDirty(),this.renderCanvas(),this.renderInspector()}}})}cardAction(e,t,s){return r("button",{class:"atfb-card__action",type:"button",title:t,attrs:{"aria-label":t},on:{click:a=>{a.stopPropagation(),s()}},children:[z(e)]})}gapAnimator(e,t,s,a){const i=r("div",{class:"atfb-marker",attrs:{"aria-hidden":"true"}});let o=null,l=null,c=0;const d=()=>{i.remove(),l?.classList.remove("atfb-lifted"),l=null,o=null},h=u=>{const b=Array.from(e.querySelectorAll(t)),g=new Map(b.filter(x=>!x.classList.contains("atfb-lifted")).map(x=>[x,x.getBoundingClientRect().top]));u();for(const x of b)x.style.transition="none",x.style.transform="";const p=b.filter(x=>{const I=g.get(x);if(I===void 0||!x.isConnected||x.classList.contains("atfb-lifted"))return!1;const S=I-x.getBoundingClientRect().top;return Math.abs(S)<.5?!1:(x.style.transform=`translateY(${S}px)`,!0)});e.offsetHeight;for(const x of b){if(!p.includes(x)){x.style.transition="";continue}x.style.transition="transform 160ms ease",x.style.transform="",x.addEventListener("transitionend",()=>{x.style.transition=""},{once:!0})}},w=u=>{const b=u.detail;if(b?.payload?.type!==se||!e.classList.contains("is-dropping")||a&&!a(b.payload.data??{}))return;const g=b.payload.data?.fieldId,p=typeof g=="string"?s(g):null,x=Je(e,t,b.clientY??0,p??void 0);x===o&&i.isConnected||(o=x,h(()=>{p&&p!==l&&(l?.classList.remove("atfb-lifted"),p.classList.add("atfb-lifted"),l=p),i.style.blockSize=`${Math.max(8,Math.round(c))}px`;const I=Array.from(e.querySelectorAll(t)).filter(S=>S!==p);x>=I.length?e.append(i):I[x].before(i)}))},_=()=>{e.classList.remove("is-dropping"),window.setTimeout(()=>h(d),0)};return document.addEventListener("os.drag.move",w),document.addEventListener("os.drag.end",_),{enter:u=>{const b=u?s(u):null;c=b&&!b.classList.contains("atfb-lifted")?b.getBoundingClientRect().height:c||44},leave:()=>h(d),drop:(u,b)=>{const g=o??Je(e,t,u,b??void 0);return d(),g},teardown:()=>{document.removeEventListener("os.drag.move",w),document.removeEventListener("os.drag.end",_),d()}}}registerCanvasTarget(e){this.canvasTarget?.(),this.canvasTarget=null;const t=this.gapAnimator(e,".atfb-card",i=>this.canvas.querySelector(`[data-atfb-card="${CSS.escape(i)}"]`)),s=Z().registerDropTarget({id:`atfb-canvas-${this.form?.id??0}`,element:e,accept:i=>i.type===se,onEnter:i=>{e.classList.add("is-dropping"),t.enter(i.payload.data.fieldId)},onLeave:()=>{e.classList.remove("is-dropping"),t.leave()},onDrop:(i,o)=>{e.classList.remove("is-dropping");const l=i.payload.data,c=l.fieldId?this.canvas.querySelector(`[data-atfb-card="${CSS.escape(l.fieldId)}"]`):null,d=t.drop(o.clientY,c);if(l.isNew&&l.fieldType){this.addField(l.fieldType,d);return}if(l.fieldId&&this.locateField(l.fieldId)){this.relocateField(l.fieldId,null,d);return}l.field&&this.insertField({...l.field,id:""},d)}}),a=this.wireRepeaterZones(e);this.canvasTarget=()=>{s(),a.forEach(i=>i()),t.teardown()}}wireRepeaterZones(e){const t=[];return e.querySelectorAll("[data-atfb-repeater-zone]").forEach(s=>{const a=s.dataset.atfbRepeaterZone??"",i=l=>{const c=l;if(c.fieldId===a)return!1;const d=c.isNew?c.fieldType:this.locateField(c.fieldId??"")?.field.type??c.field?.type;return!!d&&this.allowedInRepeater(d)},o=this.gapAnimator(s,".atfb-subcard",l=>e.querySelector(`[data-atfb-subfield="${CSS.escape(l)}"]`),i);t.push(()=>o.teardown()),t.push(Z().registerDropTarget({id:`atfb-repzone-${this.form?.id??0}-${a}`,element:s,accept:l=>l.type===se&&i(l.data),onEnter:l=>{s.classList.add("is-dropping"),o.enter(l.payload.data.fieldId)},onLeave:()=>{s.classList.remove("is-dropping"),o.leave()},onDrop:(l,c)=>{s.classList.remove("is-dropping");const d=l.payload.data,h=d.fieldId?e.querySelector(`[data-atfb-subfield="${CSS.escape(d.fieldId)}"]`):null,w=o.drop(c.clientY,h);if(d.isNew&&d.fieldType){this.addFieldToRepeater(d.fieldType,a,w);return}if(d.fieldId&&this.locateField(d.fieldId)){this.relocateField(d.fieldId,a,w);return}d.field&&this.insertFieldIntoRepeater({...d.field,id:""},a,w)}})),s.querySelectorAll(".atfb-subcard").forEach(l=>{const c=l.dataset.atfbSubfield??"";l.addEventListener("click",d=>{d.target.closest(".atfb-preview__remove")||(d.stopPropagation(),!Z().recentlyEndedDrag()&&this.selectField(c))}),l.addEventListener("keydown",d=>{if(d.stopPropagation(),d.key==="Enter"||d.key===" "){d.preventDefault(),this.selectField(c);return}if(d.altKey&&(d.key==="ArrowUp"||d.key==="ArrowDown")){d.preventDefault();const h=this.locateField(c);h&&(this.relocateField(c,a,d.key==="ArrowUp"?h.index-1:h.index+1),window.requestAnimationFrame(()=>{this.canvas.querySelector(`[data-atfb-subfield="${CSS.escape(c)}"]`)?.focus()}));return}(d.key==="Delete"||d.key==="Backspace")&&(d.preventDefault(),this.deleteField(c))}),l.addEventListener("pointerdown",d=>{if(d.stopPropagation(),d.target.closest(".atfb-preview__remove"))return;const h=this.locateField(c);h&&Z().start({payload:Ee(se,l,{fieldId:c,parentId:a,field:h.field,isNew:!1},d),origin:d})})})}),t}addField(e,t){const s=this.buildField(e);s&&this.insertField(s,t)}buildField(e){const t=this.config?.fieldTypes.find(a=>a.type===e);return!t||!this.schema?void 0:{id:this.nextFieldId(),type:e,label:t.input?t.label:"",placeholder:"",hint:"",required:!1,width:"full",cssClass:"",default:"",choices:t.choices?[{label:"First choice",value:"first"},{label:"Second choice",value:"second"}]:[],logic:{enabled:!1,action:"show",match:"all",rules:[]},messages:{},prefill:"",...t.settings}}allowedInRepeater(e){const t=this.config?.fieldTypes.find(s=>s.type===e);return!t||!t.input?!1:!["repeater","page_break","file","signature","total"].includes(e)}addFieldToRepeater(e,t,s){if(!this.allowedInRepeater(e))return;const a=this.buildField(e);a&&this.insertFieldIntoRepeater(a,t,s)}insertFieldIntoRepeater(e,t,s){const a=this.liveField(t);if(!this.schema||!a||a.type!=="repeater")return;e.id||(e.id=this.nextFieldId()),this.snapshot(),Array.isArray(a.fields)||(a.fields=[]);const i=a.fields,o=s===void 0?i.length:Math.max(0,Math.min(s,i.length));i.splice(o,0,e),this.selected=e.id,this.markDirty(),this.renderCanvas(),this.renderInspector(),window.requestAnimationFrame(()=>{this.canvas.querySelector(`[data-atfb-subfield="${CSS.escape(e.id)}"]`)?.focus()})}relocateField(e,t,s){if(!this.schema)return;const a=this.locateField(e);if(!a)return;if(!a.parent&&!t){this.moveField(e,s);return}const i=t?this.liveField(t):null;if(t&&(!i||i.type!=="repeater"||e===t))return;i&&!Array.isArray(i.fields)&&(i.fields=[]);const o=i?i.fields:this.schema.fields;this.snapshot(),a.list.splice(a.index,1);const l=Math.max(0,Math.min(s,o.length));o.splice(l,0,a.field),this.selected=a.field.id,this.markDirty(),this.renderCanvas(),this.renderInspector()}insertField(e,t){if(!this.schema)return;e.id||(e.id=this.nextFieldId()),this.snapshot();const s=t===void 0?this.schema.fields.length:Math.max(0,Math.min(t,this.schema.fields.length));this.schema.fields.splice(s,0,e),this.selected=e.id,this.markDirty(),this.renderCanvas(),this.renderInspector(),window.requestAnimationFrame(()=>{this.canvas.querySelector(`[data-atfb-card="${CSS.escape(e.id)}"]`)?.focus()})}moveField(e,t){if(!this.schema)return;const s=Wt(this.schema.fields,e,t);if(!s)return;this.snapshot();const[a]=this.schema.fields.splice(s.from,1);this.schema.fields.splice(s.to,0,a),this.markDirty(),this.renderCanvas()}duplicateField(e){if(!this.schema)return;const t=this.locateField(e);if(!t)return;const s=JSON.parse(JSON.stringify(t.field)),a=this.usedFieldIds(),i=()=>{let o=a.size+1;for(;a.has(`f${o}`);)o++;const l=`f${o}`;return a.add(l),l};s.id=i();for(const o of s.fields??[])o.id=i();if(t.parent){this.snapshot(),t.list.splice(t.index+1,0,s),this.selected=s.id,this.markDirty(),this.renderCanvas(),this.renderInspector();return}this.insertField(s,t.index+1)}async deleteField(e){if(!this.schema)return;const t=this.locateField(e);if(!t)return;const s=this.schema.fields.filter(i=>i.logic?.rules?.some(o=>o.field===e)),a=s.length?`Delete this field? ${s.length} other field${s.length===1?"":"s"} use it in a condition, and those conditions will stop working.`:Ze("confirmDelete","Delete this? It cannot be undone.");await ie(a,"Delete field")&&(this.snapshot(),t.list.splice(t.index,1),this.selected===e&&(this.selected=null),this.markDirty(),this.renderCanvas(),this.renderInspector())}selectField(e){this.selected=e;for(const t of this.canvas.querySelectorAll("[data-atfb-card]")){const s=t.dataset.atfbCard===e;t.classList.toggle("is-selected",s),t.setAttribute("aria-pressed",s?"true":"false")}for(const t of this.canvas.querySelectorAll("[data-atfb-subfield]"))t.classList.toggle("is-selected",t.dataset.atfbSubfield===e);this.logicMap?.highlight(e),this.renderInspector()}nextFieldId(){const e=this.usedFieldIds();let t=e.size+1;for(;e.has(`f${t}`);)t++;return`f${t}`}usedFieldIds(){const e=new Set;for(const t of this.schema?.fields??[]){e.add(t.id);for(const s of t.fields??[])e.add(s.id)}return e}nextEntryId(e,t){const s=new Set(t.map(i=>i.id));let a=t.length+1;for(;s.has(`${e}${a}`);)a++;return`${e}${a}`}renderTypeSettings(e,t,s,a){for(const i of s){const o=Pt[i];o&&(this.inspector.append(Bt(e,o,a)),o.also&&this.inspector.append(Bt(e,{...o,key:o.also.key,label:o.also.label,hint:o.also.hint},a)))}s.includes("rows")&&e.type!=="likert"&&this.inspector.append(y("Lines tall",G(String(e.rows??""),i=>a("rows",i)))),s.includes("rows")&&e.type==="likert"&&this.inspector.append(this.renderStatementList(e,a)),s.includes("parts")&&this.inspector.append(this.renderPartsEditor(e,t,a)),s.includes("default")&&!["files","object"].includes(t?.value??"")&&this.inspector.append(this.renderDefaultControl(e,t,a))}renderStatementList(e,t){const s=Array.isArray(e.rows)?e.rows:[];return y("Statements",pe(s.map(a=>a.label??"").join(` `),a=>t("rows",Ht(s,a)),5),"One per line. Each becomes a row of the matrix.")}renderPartsEditor(e,t,s){const a=t?.parts??[],i=Array.isArray(e.parts)?e.parts:a.map(l=>l.key),o=a.map(l=>O(l.label,i.includes(l.key),c=>{const d=a.map(h=>h.key).filter(h=>h===l.key?c:i.includes(h));s("parts",d.length?d:[l.key])}));return r("div",{class:"atfb-section",children:[r("h4",{class:"atfb-section__title",text:"Parts to ask for"}),...o]})}renderDefaultControl(e,t,s){const a="Filled in before they start. They can change it.";return e.type==="switch"||e.type==="consent"?O("On by default",!!e.default,i=>s("default",i)):t?.choices&&(e.choices??[]).length?y("Default answer",D(String(e.default??""),[{value:"",label:"Nothing chosen"},...(e.choices??[]).map(i=>({value:String(i.value),label:i.label||String(i.value)}))],i=>s("default",i)),a):y("Default answer",T(String(e.default??""),i=>s("default",i)),a)}renderChoicesEditor(e,t){const s=e.choices??[],a=o=>this.liveField(e.id)?.choices?.[o],i=r("div",{class:"atfb-choices"});return s.forEach((o,l)=>{const c=r("div",{class:"atfb-choice-row",children:[e.type==="image_choice"?this.choiceImageWell(o,l,e):null,J(T(o.label,d=>{const h=a(l);if(!h)return;const w=!h.value||h.value===h.label;h.label=d,w&&(h.value=d),this.markDirty();const _=this.liveField(e.id);_&&this.syncCanvas(_)}),`choice:${l}:label`),J(T(o.value,d=>{const h=a(l);h&&(h.value=d,this.markDirty())},"value"),`choice:${l}:value`),e.type==="quiz"||o.points!==void 0?G(String(o.points??""),d=>{const h=a(l);h&&(h.points=d===""?void 0:Number(d),this.markDirty())}):G(String(o.price??""),d=>{const h=a(l);h&&(h.price=d===""?void 0:Number(d),this.markDirty())}),r("button",{class:"atfb-card__action",type:"button",attrs:{"aria-label":`Remove ${o.label}`},on:{click:()=>{const d=this.liveField(e.id);d&&((d.choices??[]).splice(l,1),t("choices",d.choices),this.renderInspector())}},children:[z("trash")]})]});i.append(c)}),r("div",{class:"atfb-section",children:[r("h4",{text:"Choices"}),r("p",{class:"atfb-hint",text:e.type==="quiz"?"Label, value, points.":"Label, value, and a price for calculations."}),i,$("Add choice",()=>{const o=this.liveField(e.id);if(!o)return;const l=o.choices??[];l.push({label:"",value:""}),t("choices",l),this.renderInspector()},"ghost","plus-alt2"),e.type==="quiz"?y("Correct answer",D(String(e.correct??""),[{value:"",label:"—"},...s.map(o=>({value:o.value,label:o.label}))],o=>t("correct",o))):null]})}choiceImageWell(e,t,s){const a=r("div",{class:`atfb-well${e.image?" has-image":""}`,attrs:{"data-choice":t,"aria-label":`Image for ${e.label||`choice ${t+1}`}`},children:[e.image?r("span",{class:"atfb-well__id",text:`#${e.image}`}):z("format-image")]}),i=Z().registerDropTarget({id:`atfb-well-${s.id}-${t}`,element:a,accept:o=>Fn.includes(o.type),onEnter:()=>a.classList.add("is-dropping"),onLeave:()=>a.classList.remove("is-dropping"),onDrop:o=>{a.classList.remove("is-dropping");const l=o.payload.data,c=Number(l.attachmentId??l.id??l.file?.id??0);if(!c){N("That is not an image this field can use","","error");return}const d=this.liveField(s.id)?.choices?.[t];d&&(d.image=c,this.markDirty(),this.renderInspector())}});return this.teardowns.push(i),a.addEventListener("click",()=>{const o=this.liveField(s.id)?.choices?.[t];o?.image&&(o.image=void 0,this.markDirty(),this.renderInspector())}),a}renderValidationSection(e,t,s){const a=[],i=[];t.includes("minlength")&&i.push(["minlength","Minimum characters"],["maxlength","Maximum characters"]),t.includes("min")?i.push(["min","Minimum"],["max","Maximum"]):t.includes("max")&&i.push(["max","Highest"]),t.includes("step")&&i.push(["step","Steps of"]),t.includes("mindate")&&i.push(["minDate","Earliest date"],["maxDate","Latest date"]),t.includes("mintime")&&i.push(["minTime","Earliest time"],["maxTime","Latest time"]);for(const[l,c]of i)a.push(y(c,T(String(e[l]??""),d=>s(l,d))));t.includes("pattern")&&a.push(...this.renderAnswerShapeRows(e,s)),t.includes("unique")&&a.push(O("No two people may submit the same value",!!e.unique,l=>s("unique",l)));const o=e.messages??{};return t.includes("required")&&a.push(y("Message when required",T(o.required??"",l=>{o.required=l,s("messages",o)}),"Leave empty for the default wording.")),a.length?this.section(`validation:${e.id}`,"Validation",a):r("div",{class:"atfb-section is-empty"})}buildShapePicker(e,t){const s=[{heading:null,options:[{value:"",label:"Anything at all"}]},...$n.map(i=>({heading:i,options:Dt.filter(o=>o.group===i).map(o=>({value:o.slug,label:o.label}))})),{heading:"Your own",options:[{value:"custom",label:"A custom rule…"}]}];if(Y("os-select")&&Y("os-option")){const i=document.createElement("os-select");i.setAttribute("value",e),i.setAttribute("aria-label","What the answer should look like"),i.classList.add("atfb-field");for(const o of s){if(o.heading){const l=document.createElement("os-option");l.setAttribute("value",`__heading:${o.heading}`),l.setAttribute("disabled",""),l.textContent=o.heading,i.append(l)}for(const l of o.options){const c=document.createElement("os-option");c.setAttribute("value",l.value),c.textContent=l.label,i.append(c)}}return i.addEventListener("os-pick",o=>{t(String(o.detail?.value??""))}),i}const a=r("select",{class:"atfb-input atfb-select",attrs:{"aria-label":"What the answer should look like"},on:{change:i=>t(i.target.value)}});for(const i of s){const o=i.heading?(()=>{const l=document.createElement("optgroup");return l.label=i.heading,a.append(l),l})():a;for(const l of i.options)o.append(r("option",{value:l.value,text:l.label,attrs:{selected:l.value===e}}))}return a}renderAnswerShapeRows(e,t){const a=(typeof e.validation=="string"?e.validation:"")||(e.pattern?"custom":""),i=()=>Ln({root:this.root,field:this.liveField(e.id)??e,onSave:h=>{t("validation","custom"),t("pattern",h.pattern),t("validationRecipe",JSON.stringify(h.recipe));const w={...this.liveField(e.id)?.messages??{}};w.invalid=h.message,t("messages",w),this.renderInspector()},onCancel:()=>this.renderInspector()}),o=h=>{if(h==="custom"){i();return}t("validation",h),t("pattern",""),t("validationRecipe",""),this.renderInspector()},l=this.buildShapePicker(a,o),c=An(a),d=[y("The answer should be",l,c?`e.g. ${c.example}`:"Checked as they type, and again on the server.")];if(a==="custom"){const h=Tt(String(e.validationRecipe??"")),w=Mt(h)||(e.pattern?`Matches the expression ${String(e.pattern)}`:"No rule yet — open the builder.");d.push(y("Your rule",r("div",{class:"atfb-valrule",children:[r("p",{class:"atfb-valrule__words",text:w}),$("Edit the rule…",i,"secondary","edit")]}),xe(h)||e.pattern?"Built in the rule builder, with a playground to test it.":void 0))}return d}logicRulesEditor(e,t,s=""){const a=(this.schema?.fields??[]).filter(d=>d.id!==s&&d.type!=="page_break"),i=()=>r("div",{class:"atfb-rule-join",children:[r("button",{class:"atfb-rule-join__chip",type:"button",text:e.match==="all"?"and":"or",title:"Switch between needing every rule (and) or any one of them (or).",on:{click:()=>t(d=>{d.match=d.match==="all"?"any":"all"},!0)}})]}),o=(d,h)=>{const w=r("button",{class:"atfb-card__action",type:"button",attrs:{"aria-label":"Remove this rule"},title:"Remove this rule",on:{click:()=>t(u=>{u.rules.splice(h,1)},!0)},children:[z("trash")]}),_=[r("div",{class:"atfb-rule__top",children:[D(d.field,a.map(u=>({value:u.id,label:u.label||u.id})),u=>t(b=>{const g=b.rules[h];g&&(g.field=u,g.value="")},!0)),w]}),D(d.operator,Object.entries(this.config?.operators??{}).map(([u,b])=>({value:u,label:b})),u=>t(b=>{const g=b.rules[h];g&&(g.operator=u)},!0))];if(!Me.includes(d.operator)){const u=this.schema?.fields.find(b=>b.id===d.field);if(u?.choices?.length){const b=u.choices.map(g=>({value:g.value,label:g.label||g.value}));d.value!==""&&!u.choices.some(g=>g.value===d.value)&&b.unshift({value:d.value,label:d.value}),_.push(D(d.value,b,g=>t(p=>{const x=p.rules[h];x&&(x.value=g)})))}else _.push(T(d.value,b=>t(g=>{const p=g.rules[h];p&&(p.value=b)}),"The answer to compare against"))}return r("div",{class:"atfb-rule",children:_})},l=r("div",{class:"atfb-rules"});e.rules.forEach((d,h)=>{h>0&&l.append(i()),l.append(o(d,h))});const c=$("Add rule",()=>{t(d=>{d.rules.push({field:a[0]?.id??"",operator:"is",value:""})},!0)},"ghost","plus-alt2");return[l,c]}conditionsSection(e,t,s){const a=(o,l=!1)=>{o(s),this.markDirty(),l&&this.renderCanvas()},i=t==="confirmation"?"Use":"Send";return this.section(`conditions:${e}`,"Conditions",[O(`Only ${i.toLowerCase()} this ${t} sometimes`,s.enabled,o=>a(l=>{l.enabled=o},!0)),s.enabled?r("div",{children:[r("div",{class:"atfb-rule-head",children:[r("span",{text:`${i} it when`}),D(s.match,[{value:"all",label:"all"},{value:"any",label:"any"}],o=>a(l=>{l.match=o},!0)),r("span",{text:"of these match:"})]}),...this.logicRulesEditor(s,a)]}):null],s.enabled)}renderLogicSection(e){const t=e.logic,s=()=>this.liveField(e.id)?.logic,a=(o,l=!1)=>{const c=s();c&&(o(c),this.markDirty(),this.renderCanvas(),l&&this.renderInspector())},i=this.logicRulesEditor(t,a,e.id);return this.section(`logic:${e.id}`,"Conditional logic",[O("Only show this field sometimes",t.enabled,o=>{a(l=>{l.enabled=o},!0)}),t.enabled?r("div",{children:[r("div",{class:"atfb-rule-head",children:[D(t.action,[{value:"show",label:"Show"},{value:"hide",label:"Hide"}],o=>a(l=>{l.action=o},!0)),r("span",{text:"this field when"}),D(t.match,[{value:"all",label:"all"},{value:"any",label:"any"}],o=>a(l=>{l.match=o},!0)),r("span",{text:"of these match:"})]}),...i]}):null],t.enabled)}renderTabCanvas(){return this.schema?this.tab==="theme"?mt({themes:this.themes,tokens:this.config?.tokens??[],activeSlug:this.schema.settings.theme,overrides:this.schema.settings.themeOverrides,onTheme:e=>{this.schema.settings.theme=e,this.markDirty()},onOverride:(e,t)=>{t===""?delete this.schema.settings.themeOverrides[e]:this.schema.settings.themeOverrides[e]=t,this.markDirty()},onOverridesReplaced:e=>{this.schema.settings.themeOverrides={...e},this.markDirty()},previewFor:(e,t)=>this.previewHtml(e,t),onThemesChanged:e=>{this.themes=e}}):this.tab==="settings"?this.renderSettingsPane():this.tab==="notify"?this.renderNotificationsPane():this.renderConfirmationsPane():r("div")}async paintCanvasTheme(){if(!this.form||!this.schema)return;const e=this.schema.settings.theme,t=JSON.stringify([e,this.schema.settings.themeOverrides]);if(t!==this.canvasThemeSignature){this.canvasThemeSignature=t;try{const s=await this.previewHtml(e,this.schema.settings.themeOverrides??{});this.root.classList.toggle("atfb--dark-form",/atf-is-dark/.test(s));const a=/class="atf-form-wrap"[^>]*\sstyle="([^"]*)"/.exec(s);if(!a)return;const i=a[1].replace(/"/g,'"').replace(/�?39;/g,"'").replace(/&/g,"&");this.canvasTheme.textContent=`.atfb .atfb-preview { ${i} -}`,this.canvasTheme.isConnected||this.root.append(this.canvasTheme)}catch{}}}async previewHtml(e,t){if(!this.form||!this.schema)return"";const s=JSON.parse(JSON.stringify(this.schema));s.settings.theme=e,s.settings.themeOverrides=t;const{html:a}=await F.preview(this.form.id,{schema:s,theme:e});return a}renderSettingsPane(){const e=this.schema.settings,t=(s,a)=>{const i=s.split(".");let o=e;for(let l=0;lt("submitLabel",s))),O("Submit without reloading the page",e.ajax,s=>t("ajax",s)),y("Progress indicator",D(e.progressBar,[{value:"steps",label:"Numbered steps"},{value:"bar",label:"A bar"},{value:"none",label:"None"}],s=>t("progressBar",s)),"Only shown on forms with a page break.")]}),r("section",{children:[r("h3",{text:"Who can fill this in"}),O("Only logged-in users",e.requireLogin,s=>t("requireLogin",s)),y("Message for everyone else",T(e.loginMessage,s=>t("loginMessage",s))),y("Open from",r("input",{class:"atfb-input",type:"datetime-local",value:e.schedule.start,on:{input:s=>t("schedule.start",s.target.value)}})),y("Closes",r("input",{class:"atfb-input",type:"datetime-local",value:e.schedule.end,on:{input:s=>t("schedule.end",s.target.value)}})),y("Message when closed",T(e.schedule.message,s=>t("schedule.message",s))),y("Stop after this many submissions",G(String(e.limit.total||""),s=>t("limit.total",Number(s)||0)),"0 means no limit."),y("Submissions per logged-in user",G(String(e.limit.perUser||""),s=>t("limit.perUser",Number(s)||0)))]}),r("section",{children:[r("h3",{text:"Spam"}),r("p",{class:"atfb-hint",text:"No captcha. Nothing here asks the visitor to prove anything."}),O("Honeypot field",e.spam.honeypot,s=>t("spam.honeypot",s)),y("Reject submissions faster than (seconds)",G(String(e.spam.timeTrap),s=>t("spam.timeTrap",Number(s)||0)),"A human cannot fill in a form in under a second. A script can."),y("Submissions allowed per hour, per address",G(String(e.spam.rateLimit),s=>t("spam.rateLimit",Number(s)||0))),y("Blocked words",pe(e.spam.blocklist,s=>t("spam.blocklist",s),4),"One per line."),O("Use Akismet when it is installed",e.spam.akismet,s=>t("spam.akismet",s)),O("Ask a simple sum before sending",e.spam.challenge,s=>t("spam.challenge",s)),r("p",{class:"atfb-hint",text:"Only for a form under sustained attack — it is the one check here that asks the visitor to do something. Still kinder than an image captcha: it is answerable by a screen reader, and it hands no data to anyone."})]}),r("section",{children:[r("h3",{text:"Storage and privacy"}),O("Keep entries",e.storage.entries,s=>t("storage.entries",s)),O("Record IP addresses",e.storage.ip,s=>t("storage.ip",s)),O("Anonymise recorded IP addresses",e.storage.anonymise,s=>t("storage.anonymise",s)),y("Delete entries after (days)",G(String(e.storage.retention||""),s=>t("storage.retention",Number(s)||0)),"0 keeps them forever. Anything else deletes automatically, every day.")]}),r("section",{children:[r("h3",{text:"Analytics"}),O("Count views and starts",e.analytics.enabled,s=>t("analytics.enabled",s)),O("Tally device, browser and system",e.analytics.tech,s=>t("analytics.tech",s)),r("p",{class:"atfb-hint",text:"Counters, never people: no cookie, no fingerprint, no per-visitor record — the tallies keep coarse classes like “phone” and “Chrome”, not the visitor’s user-agent string. Nothing here needs a consent banner. Turning the first switch off stops view and start counting entirely."})]}),r("section",{children:[r("h3",{text:"Save and continue later"}),O("Let people save a half-finished form",e.resume.enabled,s=>t("resume.enabled",s)),y("Keep a saved form for (days)",G(String(e.resume.days),s=>t("resume.days",Math.max(1,Number(s)||30)))),r("p",{class:"atfb-hint",text:"The link this creates is the only key to those answers — anyone holding it can read them. For genuinely sensitive questions, require login instead."})]}),r("section",{children:[r("h3",{text:"Archive"}),r("p",{class:"atfb-hint",text:"Retire the form when its moment has passed. It stops accepting responses and leaves every list, and its entries and stats go with it — nothing is deleted, and restoring it brings all of it back exactly as it was."}),$("Archive this form",()=>void this.archiveCurrentForm(),"secondary","archive")]}),r("section",{children:[r("h3",{text:"Delete"}),r("p",{class:"atfb-hint",text:"Deleting is for a form that should never have existed. It moves to the desktop’s Trash, disappears from every list, and any page still carrying its shortcode shows nothing at all. Its entries stay in Entries, and until the Trash is emptied you can bring it back from there. If the form simply had its day, archive it instead."}),$("Delete this form",()=>void this.deleteCurrentForm(),"danger","trash")]})]})}async archiveCurrentForm(){if(!this.form)return;const e=this.form.title||"(untitled)",t=this.forms.find(a=>a.id===this.form.id)?.entries??0;if(await ie(`Archive “${e}”? It stops accepting responses and leaves every list — its ${t} ${t===1?"entry":"entries"} and its stats go with it. Nothing is deleted: restore it any time from “Start a new form”.`,"Archive form"))try{this.dirty&&await this.save(!0),await F.archiveForm(this.form.id),N("Form archived",`${e} — restore it any time from the New form dialog.`),await this.releaseCurrentForm()}catch(a){N("Could not archive the form",a instanceof Error?a.message:"","error")}}async deleteCurrentForm(){if(!this.form)return;const e=this.form.title||"(untitled)",t=this.forms.find(a=>a.id===this.form.id)?.entries??0;if(await ie(`Delete “${e}”? Every page showing it goes blank. ${t?`Its ${t} ${t===1?"entry stays":"entries stay"} in Entries. `:""}It moves to the desktop’s Trash — restore it from there if you change your mind.`,"Delete form"))try{const a=this.form.id;await F.deleteForm(a),window.wp?.os?.announceContentChange?.("alltfo_form","trashed",a,"allterrain-forms"),N("Form deleted",`${e} moved to the desktop’s Trash.`),await this.releaseCurrentForm()}catch(a){N("Could not delete the form",a instanceof Error?a.message:"","error")}}async releaseCurrentForm(){if(this.forms=this.forms.filter(e=>e.id!==this.form.id),this.form=null,this.schema=null,this.selected=null,this.dirty=!1,this.history=[],this.historyAt=-1,this.forms.length){await this.open(this.forms[0].id);return}this.renderBar(),this.renderCanvas(),this.renderInspector()}taggableInput(e,t,s=""){return ut(T(e,t,s),{formId:this.form.id})}taggableArea(e,t,s=6){return ut(pe(e,t,s),{formId:this.form.id})}recipientControl(e){const t=this.schema.fields.filter(c=>c.type==="email"),s=c=>{if(c.trim()==="{admin_email}")return"admin";const d=c.trim().match(/^\{field:([a-z0-9_-]+)\}$/i);return d&&t.some(h=>h.id===d[1])?`field:${d[1]}`:/\{/.test(c)?"custom":"address"},a=[{value:"admin",label:"Whoever runs this site"},...t.map(c=>({value:`field:${c.id}`,label:`The person who filled it in — ${c.label||"their email answer"}`})),{value:"address",label:"A specific email address"},{value:"custom",label:"Something else (advanced)"}],i=s(e.to),o=r("div",{class:"atfb-recipient__detail"}),l=c=>{if(o.replaceChildren(),c==="address"){o.append(T(/\{/.test(e.to)?"":e.to,d=>{e.to=d,this.markDirty()},"name@example.com"));return}c==="custom"&&o.append(this.taggableInput(e.to,d=>{e.to=d,this.markDirty()},"{admin_email}, sales@example.com"),r("p",{class:"atfb-row__hint",text:"Separate several addresses with commas."}))};return l(i),y("Send it to",r("div",{class:"atfb-recipient",children:[D(i,a,c=>{c==="admin"?e.to="{admin_email}":c.startsWith("field:")?e.to=`{${c}}`:c==="address"&&(e.to=/\{/.test(e.to)?"":e.to),this.markDirty(),l(c)}),o]}),t.length?void 0:"Add an Email question on the Build tab to reply straight back to the visitor.")}renderNotificationsPane(){const e=this.schema.notifications,t=r("div",{class:"atfb-list"});return e.length||t.append(r("p",{class:"atfb-hint",text:"With none set up, one email goes to the site administrator with every answer in it."})),e.forEach((s,a)=>{t.append(this.section(`notification:${s.id}`,s.name||`Notification ${a+1}`,[y("Name",T(s.name,i=>{s.name=i,this.markDirty()})),this.recipientControl(s),y("Reply to",this.taggableInput(s.replyTo,i=>{s.replyTo=i,this.markDirty()},"Leave empty to reply to you"),"Set this to the visitor’s email address and hitting Reply answers them directly."),y("Subject",this.taggableInput(s.subject,i=>{s.subject=i,this.markDirty()})),y("Message",this.taggableArea(s.message,i=>{s.message=i,this.markDirty()},8)),O("Attach uploaded files",s.attachFiles,i=>{s.attachFiles=i,this.markDirty()}),this.conditionsSection(s.id,"notification",s.logic),$("Delete this notification",()=>{e.splice(a,1),this.markDirty(),this.renderCanvas()},"danger")]))}),r("div",{class:"atfb-pane",children:[r("h2",{text:"Notifications"}),t,$("Add a notification",()=>{e.push({id:this.nextEntryId("n",e),enabled:!0,name:"Notification",to:"{admin_email}",cc:"",bcc:"",replyTo:"",fromName:"",fromEmail:"",subject:"New submission",message:"{all_fields}",attachFiles:!1,logic:{enabled:!1,action:"show",match:"all",rules:[]}}),this.markDirty(),this.renderCanvas()},"primary","plus-alt2")]})}confirmationDetail(e){if(e.type==="message")return r("div",{children:[y("Message",this.taggableArea(e.message,i=>{e.message=i,this.markDirty()},5),"Insert an answer to greet them by name, or show back what they sent."),this.successDesigner(e)]});const t=y("Extra query parameters",this.taggableInput(e.query,i=>{e.query=i,this.markDirty()},"ref={entry:id}&name={field:f1}"),"Added to the address, so the page they land on can read them. Leave empty for none.");if(e.type==="redirect")return r("div",{children:[y("Web address",this.taggableInput(e.url,i=>{e.url=i,this.markDirty()},"https://example.com/thank-you"),"A full address, starting with https://."),t]});const s=r("div",{class:"atfb-pagepicker"}),a=i=>{s.replaceChildren(D(String(e.pageId||0),i,o=>{e.pageId=Number(o)||0,this.markDirty()}))};return a([{value:"0",label:"Loading pages…"}]),F.pages().then(i=>{a([{value:"0",label:"Choose a page…"},...i.map(o=>({value:String(o.id),label:o.title}))])}).catch(()=>{a([{value:"0",label:"Could not load the pages"}])}),y("Page",s,"They are sent to this page after submitting. Its own content is shown, not the form’s message.")}successDesigner(e){e.success=je(e.success);const t=e.success,s=r("div",{class:"atfb-success"}),a=[{key:"plain",label:"Plain",glyph:"¶",blurb:"Just the message."},{key:"simple",label:"Simple",glyph:"✓",blurb:"Check mark and a gentle fade."},{key:"minimal",label:"Minimalistic",glyph:"—",blurb:"Quiet type, generous space."},{key:"card",label:"Card",glyph:"🎫",blurb:"An elevated card that pops in."},{key:"check",label:"Check mark",glyph:"✔",blurb:"A big check draws itself."},{key:"confetti",label:"Confetti",glyph:"🎉",blurb:"Paper rains over the page."},{key:"fireworks",label:"Fireworks",glyph:"🎆",blurb:"The full night-sky show."},{key:"sparkles",label:"Sparkles",glyph:"✨",blurb:"Your emoji floats up around it."},{key:"typewriter",label:"Typewriter",glyph:"⌨",blurb:"Types itself out, letter by letter."}],i=()=>{const o=r("div",{class:"atfb-success__styles",attrs:{role:"radiogroup","aria-label":"Success screen style"},children:a.map(c=>r("button",{class:`atfb-success__style${t.style===c.key?" is-selected":""}`,type:"button",attrs:{role:"radio","aria-checked":t.style===c.key?"true":"false",title:c.blurb},children:[r("span",{class:"atfb-success__style-glyph",text:c.glyph}),r("span",{class:"atfb-success__style-label",text:c.label})],on:{click:()=>{t.style=c.key,this.markDirty(),i(),c.key!=="plain"&&this.previewSuccessScreen(e)}}}))}),l=[];t.style!=="plain"&&(l.push(y("Heading",this.taggableInput(t.title,c=>{t.title=c,this.markDirty()},"Thank you, {field:name}!"),"Shown above the message. Leave empty for none.")),["simple","card","confetti","fireworks","sparkles"].includes(t.style)&&l.push(y("Emoji",T(t.icon,c=>{t.icon=c,this.markDirty()},ve[t.style]||"🎉"),t.style==="sparkles"?"Also the particle that floats up — try 🎈 or ❤️.":"The badge above the heading.")),l.push(this.successAccentRow(t)),["confetti","fireworks","sparkles"].includes(t.style)&&l.push(y("Intensity",D(t.intensity,[{value:"low",label:"Calm"},{value:"medium",label:"Festive"},{value:"high",label:"Over the top"}],c=>{t.intensity=c,this.markDirty()}))),l.push(O("Offer to fill it in again",t.showButton,c=>{t.showButton=c,this.markDirty(),i()})),t.showButton&&l.push(y("Button label",T(t.buttonLabel,c=>{t.buttonLabel=c,this.markDirty()},"Fill it in again")))),s.replaceChildren(r("div",{class:"atfb-success__head",children:[r("h4",{text:"Success screen"}),$("Preview",()=>this.previewSuccessScreen(e),"ghost","controls-play")]}),o,...l)};return i(),s}successAccentRow(e){const t=r("input",{class:"atfb-success__accent",attrs:{type:"color","aria-label":"Accent colour"}});t.value=e.accent||"#3858e9";const s=$("Use the theme accent",()=>{e.accent="",t.value="#3858e9",s.style.display="none",this.markDirty()},"ghost");return s.style.display=e.accent?"":"none",t.addEventListener("input",()=>{e.accent=t.value,s.style.display="",this.markDirty()}),y("Accent",r("div",{class:"atfb-success__accent-row",children:[t,s]}),"Recolours the screen; the theme decides when left alone.")}previewSuccessScreen(e){const t=e.message||"Thank you. Your submission has been received.";let s=()=>{};const a=r("div",{class:"atfb-success-preview",attrs:{role:"dialog","aria-label":"Success screen preview","aria-modal":"true"}}),i=()=>{s(),a.remove(),document.removeEventListener("keydown",o)},o=h=>{h.key==="Escape"&&(h.stopPropagation(),i())},l=Zs(t,e.success,i),c=r("div",{class:"atfb-success-preview__stage atfb-preview atf-form",children:[l]}),d=()=>{s(),s=Ks(l,e.success)};a.append(c,r("div",{class:"atfb-success-preview__bar",children:[$("Replay",d,"secondary","controls-repeat"),$("Close",i,"primary")]})),a.addEventListener("click",h=>{h.target===a&&i()}),document.addEventListener("keydown",o),this.root.append(a),window.requestAnimationFrame(d)}renderConfirmationsPane(){const e=this.schema.confirmations,t=r("div",{class:"atfb-list"});return e.length||t.append(r("p",{class:"atfb-hint",text:"With none set up, the form says thank you and stops."})),e.forEach((s,a)=>{const i=r("div",{class:"atfb-confirm__detail"}),o=()=>{i.replaceChildren(this.confirmationDetail(s))};o(),t.append(this.section(`confirmation:${s.id}`,s.name||`Confirmation ${a+1}`,[y("Name",T(s.name,l=>{s.name=l,this.markDirty()})),y("What happens",D(s.type,[{value:"message",label:"Show a message"},{value:"redirect",label:"Send them to a URL"},{value:"page",label:"Send them to a page"}],l=>{s.type=l,this.markDirty(),o()})),i,this.conditionsSection(s.id,"confirmation",s.logic),$("Delete this confirmation",()=>{e.splice(a,1),this.markDirty(),this.renderCanvas()},"danger")]))}),r("div",{class:"atfb-pane",children:[r("h2",{text:"Confirmations"}),r("p",{class:"atfb-hint",text:"The first one whose conditions match is the one they see."}),t,$("Add a confirmation",()=>{e.push({id:this.nextEntryId("c",e),enabled:!0,name:"Confirmation",type:"message",message:"Thank you. Your submission has been received.",url:"",pageId:0,query:"",success:yt(),logic:{enabled:!1,action:"show",match:"all",rules:[]}}),this.markDirty(),this.renderCanvas()},"primary","plus-alt2")]})}async preview(){await Ot({current:()=>this.form?{id:this.form.id,title:this.form.title,previewUrl:this.form.previewUrl}:null,isDirty:()=>this.dirty,save:()=>this.save(!0)})}}let ae=null,Ke=null;document.addEventListener("alltfo-open-form",n=>{const e=Number(n.detail?.formId??0);e&&ae?.openFormById(e)});function Ve(){if(Ke?.isConnected)return;ae&&(ae.destroy(),ae=null,Ke=null);const n=document.querySelector("[data-atfb-root]:not([data-atfb-mounted])");n&&(n.dataset.atfbMounted="1",Ke=n,Ae(n),ns().then(()=>{n.isConnected&&(ae=new Wt(n),ae.start())}))}function Ut(){Ve(),hs()}return us(),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",Ut):Ut(),document.addEventListener("os-window-content-loaded",Ve),ee.Builder=Wt,ee.SETTINGS_HANDLED_ELSEWHERE=In,ee.SETTING_CONTROLS=Pt,ee.fieldMove=jt,ee.mountBuilder=Ve,ee.restatement=Ht,Object.defineProperty(ee,Symbol.toStringTag,{value:"Module"}),ee}({}); +}`,this.canvasTheme.isConnected||this.root.append(this.canvasTheme)}catch{}}}async previewHtml(e,t){if(!this.form||!this.schema)return"";const s=JSON.parse(JSON.stringify(this.schema));s.settings.theme=e,s.settings.themeOverrides=t;const{html:a}=await F.preview(this.form.id,{schema:s,theme:e});return a}renderSettingsPane(){const e=this.schema.settings,t=(s,a)=>{const i=s.split(".");let o=e;for(let l=0;lt("submitLabel",s))),O("Submit without reloading the page",e.ajax,s=>t("ajax",s)),y("Progress indicator",D(e.progressBar,[{value:"steps",label:"Numbered steps"},{value:"bar",label:"A bar"},{value:"none",label:"None"}],s=>t("progressBar",s)),"Only shown on forms with a page break.")]}),r("section",{children:[r("h3",{text:"Who can fill this in"}),O("Only logged-in users",e.requireLogin,s=>t("requireLogin",s)),y("Message for everyone else",T(e.loginMessage,s=>t("loginMessage",s))),y("Open from",r("input",{class:"atfb-input",type:"datetime-local",value:e.schedule.start,on:{input:s=>t("schedule.start",s.target.value)}})),y("Closes",r("input",{class:"atfb-input",type:"datetime-local",value:e.schedule.end,on:{input:s=>t("schedule.end",s.target.value)}})),y("Message when closed",T(e.schedule.message,s=>t("schedule.message",s))),y("Stop after this many submissions",G(String(e.limit.total||""),s=>t("limit.total",Number(s)||0)),"0 means no limit."),y("Submissions per logged-in user",G(String(e.limit.perUser||""),s=>t("limit.perUser",Number(s)||0)))]}),r("section",{children:[r("h3",{text:"Spam"}),r("p",{class:"atfb-hint",text:"No captcha. Nothing here asks the visitor to prove anything."}),O("Honeypot field",e.spam.honeypot,s=>t("spam.honeypot",s)),y("Reject submissions faster than (seconds)",G(String(e.spam.timeTrap),s=>t("spam.timeTrap",Number(s)||0)),"A human cannot fill in a form in under a second. A script can."),y("Submissions allowed per hour, per address",G(String(e.spam.rateLimit),s=>t("spam.rateLimit",Number(s)||0))),y("Blocked words",pe(e.spam.blocklist,s=>t("spam.blocklist",s),4),"One per line."),O("Check submissions with Akismet",e.spam.akismet,s=>t("spam.akismet",s)),r("p",{class:"atfb-hint",text:"Off unless you switch it on. When on, each submission — the answers, plus the sender’s IP address and user agent — is sent to Akismet’s servers for a spam verdict. Needs the Akismet plugin installed and configured."}),O("Ask a simple sum before sending",e.spam.challenge,s=>t("spam.challenge",s)),r("p",{class:"atfb-hint",text:"Only for a form under sustained attack — it is the one check here that asks the visitor to do something. Still kinder than an image captcha: it is answerable by a screen reader, and it hands no data to anyone."})]}),r("section",{children:[r("h3",{text:"Storage and privacy"}),O("Keep entries",e.storage.entries,s=>t("storage.entries",s)),O("Record IP addresses",e.storage.ip,s=>t("storage.ip",s)),O("Anonymise recorded IP addresses",e.storage.anonymise,s=>t("storage.anonymise",s)),y("Delete entries after (days)",G(String(e.storage.retention||""),s=>t("storage.retention",Number(s)||0)),"0 keeps them forever. Anything else deletes automatically, every day.")]}),r("section",{children:[r("h3",{text:"Analytics"}),O("Count views and starts",e.analytics.enabled,s=>t("analytics.enabled",s)),O("Tally device, browser and system",e.analytics.tech,s=>t("analytics.tech",s)),r("p",{class:"atfb-hint",text:"Counters, never people: no cookie, no fingerprint, no per-visitor record — the tallies keep coarse classes like “phone” and “Chrome”, not the visitor’s user-agent string. Nothing here needs a consent banner. Turning the first switch off stops view and start counting entirely."})]}),r("section",{children:[r("h3",{text:"Save and continue later"}),O("Let people save a half-finished form",e.resume.enabled,s=>t("resume.enabled",s)),y("Keep a saved form for (days)",G(String(e.resume.days),s=>t("resume.days",Math.max(1,Number(s)||30)))),r("p",{class:"atfb-hint",text:"The link this creates is the only key to those answers — anyone holding it can read them. For genuinely sensitive questions, require login instead."})]}),r("section",{children:[r("h3",{text:"Archive"}),r("p",{class:"atfb-hint",text:"Retire the form when its moment has passed. It stops accepting responses and leaves every list, and its entries and stats go with it — nothing is deleted, and restoring it brings all of it back exactly as it was."}),$("Archive this form",()=>void this.archiveCurrentForm(),"secondary","archive")]}),r("section",{children:[r("h3",{text:"Delete"}),r("p",{class:"atfb-hint",text:"Deleting is for a form that should never have existed. It moves to the desktop’s Trash, disappears from every list, and any page still carrying its shortcode shows nothing at all. Its entries stay in Entries, and until the Trash is emptied you can bring it back from there. If the form simply had its day, archive it instead."}),$("Delete this form",()=>void this.deleteCurrentForm(),"danger","trash")]})]})}async archiveCurrentForm(){if(!this.form)return;const e=this.form.title||"(untitled)",t=this.forms.find(a=>a.id===this.form.id)?.entries??0;if(await ie(`Archive “${e}”? It stops accepting responses and leaves every list — its ${t} ${t===1?"entry":"entries"} and its stats go with it. Nothing is deleted: restore it any time from “Start a new form”.`,"Archive form"))try{this.dirty&&await this.save(!0),await F.archiveForm(this.form.id),N("Form archived",`${e} — restore it any time from the New form dialog.`),await this.releaseCurrentForm()}catch(a){N("Could not archive the form",a instanceof Error?a.message:"","error")}}async deleteCurrentForm(){if(!this.form)return;const e=this.form.title||"(untitled)",t=this.forms.find(a=>a.id===this.form.id)?.entries??0;if(await ie(`Delete “${e}”? Every page showing it goes blank. ${t?`Its ${t} ${t===1?"entry stays":"entries stay"} in Entries. `:""}It moves to the desktop’s Trash — restore it from there if you change your mind.`,"Delete form"))try{const a=this.form.id;await F.deleteForm(a),window.wp?.os?.announceContentChange?.("alltfo_form","trashed",a,"allterrain-forms"),N("Form deleted",`${e} moved to the desktop’s Trash.`),await this.releaseCurrentForm()}catch(a){N("Could not delete the form",a instanceof Error?a.message:"","error")}}async releaseCurrentForm(){if(this.forms=this.forms.filter(e=>e.id!==this.form.id),this.form=null,this.schema=null,this.selected=null,this.dirty=!1,this.history=[],this.historyAt=-1,this.forms.length){await this.open(this.forms[0].id);return}this.renderBar(),this.renderCanvas(),this.renderInspector()}taggableInput(e,t,s=""){return ut(T(e,t,s),{formId:this.form.id})}taggableArea(e,t,s=6){return ut(pe(e,t,s),{formId:this.form.id})}recipientControl(e){const t=this.schema.fields.filter(c=>c.type==="email"),s=c=>{if(c.trim()==="{admin_email}")return"admin";const d=c.trim().match(/^\{field:([a-z0-9_-]+)\}$/i);return d&&t.some(h=>h.id===d[1])?`field:${d[1]}`:/\{/.test(c)?"custom":"address"},a=[{value:"admin",label:"Whoever runs this site"},...t.map(c=>({value:`field:${c.id}`,label:`The person who filled it in — ${c.label||"their email answer"}`})),{value:"address",label:"A specific email address"},{value:"custom",label:"Something else (advanced)"}],i=s(e.to),o=r("div",{class:"atfb-recipient__detail"}),l=c=>{if(o.replaceChildren(),c==="address"){o.append(T(/\{/.test(e.to)?"":e.to,d=>{e.to=d,this.markDirty()},"name@example.com"));return}c==="custom"&&o.append(this.taggableInput(e.to,d=>{e.to=d,this.markDirty()},"{admin_email}, sales@example.com"),r("p",{class:"atfb-row__hint",text:"Separate several addresses with commas."}))};return l(i),y("Send it to",r("div",{class:"atfb-recipient",children:[D(i,a,c=>{c==="admin"?e.to="{admin_email}":c.startsWith("field:")?e.to=`{${c}}`:c==="address"&&(e.to=/\{/.test(e.to)?"":e.to),this.markDirty(),l(c)}),o]}),t.length?void 0:"Add an Email question on the Build tab to reply straight back to the visitor.")}renderNotificationsPane(){const e=this.schema.notifications,t=r("div",{class:"atfb-list"});return e.length||t.append(r("p",{class:"atfb-hint",text:"With none set up, one email goes to the site administrator with every answer in it."})),e.forEach((s,a)=>{t.append(this.section(`notification:${s.id}`,s.name||`Notification ${a+1}`,[y("Name",T(s.name,i=>{s.name=i,this.markDirty()})),this.recipientControl(s),y("Reply to",this.taggableInput(s.replyTo,i=>{s.replyTo=i,this.markDirty()},"Leave empty to reply to you"),"Set this to the visitor’s email address and hitting Reply answers them directly."),y("Subject",this.taggableInput(s.subject,i=>{s.subject=i,this.markDirty()})),y("Message",this.taggableArea(s.message,i=>{s.message=i,this.markDirty()},8)),O("Attach uploaded files",s.attachFiles,i=>{s.attachFiles=i,this.markDirty()}),this.conditionsSection(s.id,"notification",s.logic),$("Delete this notification",()=>{e.splice(a,1),this.markDirty(),this.renderCanvas()},"danger")]))}),r("div",{class:"atfb-pane",children:[r("h2",{text:"Notifications"}),t,$("Add a notification",()=>{e.push({id:this.nextEntryId("n",e),enabled:!0,name:"Notification",to:"{admin_email}",cc:"",bcc:"",replyTo:"",fromName:"",fromEmail:"",subject:"New submission",message:"{all_fields}",attachFiles:!1,logic:{enabled:!1,action:"show",match:"all",rules:[]}}),this.markDirty(),this.renderCanvas()},"primary","plus-alt2")]})}confirmationDetail(e){if(e.type==="message")return r("div",{children:[y("Message",this.taggableArea(e.message,i=>{e.message=i,this.markDirty()},5),"Insert an answer to greet them by name, or show back what they sent."),this.successDesigner(e)]});const t=y("Extra query parameters",this.taggableInput(e.query,i=>{e.query=i,this.markDirty()},"ref={entry:id}&name={field:f1}"),"Added to the address, so the page they land on can read them. Leave empty for none.");if(e.type==="redirect")return r("div",{children:[y("Web address",this.taggableInput(e.url,i=>{e.url=i,this.markDirty()},"https://example.com/thank-you"),"A full address, starting with https://."),t]});const s=r("div",{class:"atfb-pagepicker"}),a=i=>{s.replaceChildren(D(String(e.pageId||0),i,o=>{e.pageId=Number(o)||0,this.markDirty()}))};return a([{value:"0",label:"Loading pages…"}]),F.pages().then(i=>{a([{value:"0",label:"Choose a page…"},...i.map(o=>({value:String(o.id),label:o.title}))])}).catch(()=>{a([{value:"0",label:"Could not load the pages"}])}),y("Page",s,"They are sent to this page after submitting. Its own content is shown, not the form’s message.")}successDesigner(e){e.success=We(e.success);const t=e.success,s=r("div",{class:"atfb-success"}),a=[{key:"plain",label:"Plain",glyph:"¶",blurb:"Just the message."},{key:"simple",label:"Simple",glyph:"✓",blurb:"Check mark and a gentle fade."},{key:"minimal",label:"Minimalistic",glyph:"—",blurb:"Quiet type, generous space."},{key:"card",label:"Card",glyph:"🎫",blurb:"An elevated card that pops in."},{key:"check",label:"Check mark",glyph:"✔",blurb:"A big check draws itself."},{key:"confetti",label:"Confetti",glyph:"🎉",blurb:"Paper rains over the page."},{key:"fireworks",label:"Fireworks",glyph:"🎆",blurb:"The full night-sky show."},{key:"sparkles",label:"Sparkles",glyph:"✨",blurb:"Your emoji floats up around it."},{key:"typewriter",label:"Typewriter",glyph:"⌨",blurb:"Types itself out, letter by letter."}],i=()=>{const o=r("div",{class:"atfb-success__styles",attrs:{role:"radiogroup","aria-label":"Success screen style"},children:a.map(c=>r("button",{class:`atfb-success__style${t.style===c.key?" is-selected":""}`,type:"button",attrs:{role:"radio","aria-checked":t.style===c.key?"true":"false",title:c.blurb},children:[r("span",{class:"atfb-success__style-glyph",text:c.glyph}),r("span",{class:"atfb-success__style-label",text:c.label})],on:{click:()=>{t.style=c.key,this.markDirty(),i(),c.key!=="plain"&&this.previewSuccessScreen(e)}}}))}),l=[];t.style!=="plain"&&(l.push(y("Heading",this.taggableInput(t.title,c=>{t.title=c,this.markDirty()},"Thank you, {field:name}!"),"Shown above the message. Leave empty for none.")),["simple","card","confetti","fireworks","sparkles"].includes(t.style)&&l.push(y("Emoji",T(t.icon,c=>{t.icon=c,this.markDirty()},ve[t.style]||"🎉"),t.style==="sparkles"?"Also the particle that floats up — try 🎈 or ❤️.":"The badge above the heading.")),l.push(this.successAccentRow(t)),["confetti","fireworks","sparkles"].includes(t.style)&&l.push(y("Intensity",D(t.intensity,[{value:"low",label:"Calm"},{value:"medium",label:"Festive"},{value:"high",label:"Over the top"}],c=>{t.intensity=c,this.markDirty()}))),l.push(O("Offer to fill it in again",t.showButton,c=>{t.showButton=c,this.markDirty(),i()})),t.showButton&&l.push(y("Button label",T(t.buttonLabel,c=>{t.buttonLabel=c,this.markDirty()},"Fill it in again")))),s.replaceChildren(r("div",{class:"atfb-success__head",children:[r("h4",{text:"Success screen"}),$("Preview",()=>this.previewSuccessScreen(e),"ghost","controls-play")]}),o,...l)};return i(),s}successAccentRow(e){const t=r("input",{class:"atfb-success__accent",attrs:{type:"color","aria-label":"Accent colour"}});t.value=e.accent||"#3858e9";const s=$("Use the theme accent",()=>{e.accent="",t.value="#3858e9",s.style.display="none",this.markDirty()},"ghost");return s.style.display=e.accent?"":"none",t.addEventListener("input",()=>{e.accent=t.value,s.style.display="",this.markDirty()}),y("Accent",r("div",{class:"atfb-success__accent-row",children:[t,s]}),"Recolours the screen; the theme decides when left alone.")}previewSuccessScreen(e){const t=e.message||"Thank you. Your submission has been received.";let s=()=>{};const a=r("div",{class:"atfb-success-preview",attrs:{role:"dialog","aria-label":"Success screen preview","aria-modal":"true"}}),i=()=>{s(),a.remove(),document.removeEventListener("keydown",o)},o=h=>{h.key==="Escape"&&(h.stopPropagation(),i())},l=Zs(t,e.success,i),c=r("div",{class:"atfb-success-preview__stage atfb-preview atf-form",children:[l]}),d=()=>{s(),s=Ks(l,e.success)};a.append(c,r("div",{class:"atfb-success-preview__bar",children:[$("Replay",d,"secondary","controls-repeat"),$("Close",i,"primary")]})),a.addEventListener("click",h=>{h.target===a&&i()}),document.addEventListener("keydown",o),this.root.append(a),window.requestAnimationFrame(d)}renderConfirmationsPane(){const e=this.schema.confirmations,t=r("div",{class:"atfb-list"});return e.length||t.append(r("p",{class:"atfb-hint",text:"With none set up, the form says thank you and stops."})),e.forEach((s,a)=>{const i=r("div",{class:"atfb-confirm__detail"}),o=()=>{i.replaceChildren(this.confirmationDetail(s))};o(),t.append(this.section(`confirmation:${s.id}`,s.name||`Confirmation ${a+1}`,[y("Name",T(s.name,l=>{s.name=l,this.markDirty()})),y("What happens",D(s.type,[{value:"message",label:"Show a message"},{value:"redirect",label:"Send them to a URL"},{value:"page",label:"Send them to a page"}],l=>{s.type=l,this.markDirty(),o()})),i,this.conditionsSection(s.id,"confirmation",s.logic),$("Delete this confirmation",()=>{e.splice(a,1),this.markDirty(),this.renderCanvas()},"danger")]))}),r("div",{class:"atfb-pane",children:[r("h2",{text:"Confirmations"}),r("p",{class:"atfb-hint",text:"The first one whose conditions match is the one they see."}),t,$("Add a confirmation",()=>{e.push({id:this.nextEntryId("c",e),enabled:!0,name:"Confirmation",type:"message",message:"Thank you. Your submission has been received.",url:"",pageId:0,query:"",success:yt(),logic:{enabled:!1,action:"show",match:"all",rules:[]}}),this.markDirty(),this.renderCanvas()},"primary","plus-alt2")]})}async preview(){await Ot({current:()=>this.form?{id:this.form.id,title:this.form.title,previewUrl:this.form.previewUrl}:null,isDirty:()=>this.dirty,save:()=>this.save(!0)})}}let ae=null,Ke=null;document.addEventListener("alltfo-open-form",n=>{const e=Number(n.detail?.formId??0);e&&ae?.openFormById(e)});function Ve(){if(Ke?.isConnected)return;ae&&(ae.destroy(),ae=null,Ke=null);const n=document.querySelector("[data-atfb-root]:not([data-atfb-mounted])");n&&(n.dataset.atfbMounted="1",Ke=n,Ae(n),ns().then(()=>{n.isConnected&&(ae=new jt(n),ae.start())}))}function Ut(){Ve(),hs()}return us(),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",Ut):Ut(),document.addEventListener("os-window-content-loaded",Ve),ee.Builder=jt,ee.SETTINGS_HANDLED_ELSEWHERE=In,ee.SETTING_CONTROLS=Pt,ee.fieldMove=Wt,ee.mountBuilder=Ve,ee.restatement=Ht,Object.defineProperty(ee,Symbol.toStringTag,{value:"Module"}),ee}({}); diff --git a/docs/abilities.md b/docs/abilities.md index 8dafb67..89d7840 100644 --- a/docs/abilities.md +++ b/docs/abilities.md @@ -19,24 +19,32 @@ note. | Ability | Does | Permission | |---|---|---| -| `list-forms` | Every form with id, title, status, theme, shortcode, entry count and distilled fields. The starting point: its ids feed everything else. | read entries *or* edit forms | -| `get-form` | One form in the same shape. Read it before submitting or interpreting entries — values are keyed by these field ids. | read entries *or* edit forms | +| `list-forms` | Every form with id, title, status, theme, shortcode, entry count and distilled fields. The starting point: its ids feed everything else. | read entries *or* edit forms; lists only forms the per-form gate lets the caller read | +| `get-form` | One form in the same shape. Read it before submitting or interpreting entries — values are keyed by these field ids. | edit forms, *or* read entries **for that form** | | `list-field-types` | The vocabulary for building: every registered type with slug, label, description, group and value shape. | edit forms | | `create-form` | Builds a form from a title and a loose field list (`{ type, label, required?, hint?, placeholder?, choices? }`); ids are minted, defaults seeded, shortcode returned. Fields go through the same normaliser a JSON import does. | edit forms | | `set-form-theme` | Applies an installed theme to a form; refuses invented slugs rather than falling back. | edit forms | -| `list-entries` | Queries submissions with search, date range, status, starred and pagination; answers come back raw (by field id) and readable (by label). | read entries | -| `get-entry` | One submission in full: label, raw value and formatted value per question. | read entries | -| `submit-form` | Submits through the visitor pipeline — availability, validation, anti-spam, storage, notifications. Refusals return per-field errors and store nothing. | anyone the channel authenticates | -| `form-report` | The analytics report as data: counts, rates, timeline, distributions, NPS, optional `group_by` breakdown. Prefer it over fetching every entry when the job is summarising. | read entries | +| `list-entries` | Queries submissions with search, date range, status, starred and pagination; answers come back raw (by field id) and readable (by label). | read entries **for that form** | +| `get-entry` | One submission in full: label, raw value and formatted value per question. | read entries (checked against the entry's form) | +| `submit-form` | Submits through the visitor pipeline — availability, validation, anti-spam, storage, notifications. Refusals return per-field errors and store nothing. | any authenticated user | +| `form-report` | The analytics report as data: counts, rates, timeline, distributions, NPS, optional `group_by` breakdown. Prefer it over fetching every entry when the job is summarising. | read entries **for that form** | + +Where a permission says **for that form**, the `alltfo_can_read_entries` +filter is asked with the `form_id` from the ability's input — so a site that +confines a user to a department's forms through that filter confines these +abilities the same way. ## The one honest liberty `submit-form` mints a valid time-trap signature stamped a minute in the past. The trap exists to catch bots that post faster than a human can read; an agent -invoking a described ability through an authenticated channel is not that -traffic. Every other check — honeypot, rate limit, blocklist, Akismet, and all -of validation — runs exactly as it does for a visitor, and spam verdicts still -file the entry as spam rather than discarding it. +invoking a described ability is not that traffic — which is exactly why the +ability requires an authenticated user, and an anonymous caller is refused +before this liberty is taken. Anonymous traffic has the rendered form and the +public REST `/submit` route, where the trap runs whole. Every other check — +honeypot, rate limit, blocklist, Akismet, and all of validation — runs exactly +as it does for a visitor, and spam verdicts still file the entry as spam +rather than discarding it. ## Trying one diff --git a/docs/hooks-reference.md b/docs/hooks-reference.md index 7b345fd..3feca0f 100644 --- a/docs/hooks-reference.md +++ b/docs/hooks-reference.md @@ -480,8 +480,11 @@ entry under spam rather than rejecting it, so a wrong answer here is always recoverable from the Entries window. The five checks are the honeypot, a **signed** time trap, a per-address rate -limit, a word blocklist and Akismet. A sixth — an arithmetic challenge — is off -by default and is the only one that asks the visitor to do anything. +limit, a word blocklist and Akismet. Akismet is off by default — switching it +on per form is the site owner's decision, because it sends each submission +(answers, IP, user agent) to Akismet's servers. A sixth check — an arithmetic +challenge — is also off by default and is the only one that asks the visitor +to do anything. The challenge's answer is signed with `alltfo_sign_challenge()` and never sent to the browser. A challenge whose expected answer travels in the page alongside the @@ -513,6 +516,11 @@ sending a different one each time. do_action( 'alltfo_before_submission', int $form_id, array $request, array $schema ); ``` +Since 0.4.0 `$request` arrives deep-sanitised — every key and scalar has been +through `alltfo_sanitize_request()` before this fires. The per-field-type +sanitisation still runs after it, so treat these values as clean text, not as +the typed values `alltfo_entry_created` receives. + ### `alltfo_entry_created` — Action — *Stable* ```php @@ -1074,6 +1082,10 @@ apply_filters( 'alltfo_can_read_entries', bool $can, int $form_id ); The seam for per-form permissions: a site can let a department read only the entries of the forms it owns by returning false for every other id. +The abilities honour it with the form in hand: `get-form`, `list-entries` and +`form-report` ask this filter with the `form_id` from their input, and +`list-forms` asks it per form before listing one. + --- ## Assets diff --git a/includes/abilities.php b/includes/abilities.php index 4fd463f..ea290c3 100644 --- a/includes/abilities.php +++ b/includes/abilities.php @@ -131,6 +131,28 @@ function alltfo_register_abilities() { return alltfo_can_read_entries() || alltfo_can_edit_forms(); }; + // The per-form gates. An ability that names a form in its input asks the + // question about *that* form, so the `alltfo_can_read_entries` filter — the + // seam a site uses to confine a department to its own forms — is consulted + // with the id, not with the blanket 0. The Abilities API hands the input to + // the permission callback whenever an input schema is declared. + $read_form = static function ( $input ) { + return alltfo_can_edit_forms() || alltfo_can_read_entries( absint( $input['form_id'] ?? 0 ) ); + }; + + $read_form_entries = static function ( $input ) { + return alltfo_can_read_entries( absint( $input['form_id'] ?? 0 ) ); + }; + + // Submitting is what visitors do, so no capability is asked for — but the + // ability *is* held to an authenticated user, because it mints the time-trap + // signature a rendered form would carry, and that liberty is only honest on + // a channel that identifies its caller. Anonymous traffic has the rendered + // form and the public REST route, where every anti-bot check runs whole. + $is_authenticated = static function () { + return is_user_logged_in(); + }; + wp_register_ability( 'allterrain-forms/list-forms', array( @@ -159,7 +181,7 @@ function alltfo_register_abilities() { 'label' => __( 'Get a form', 'allterrain-forms' ), 'description' => __( 'Reads one form: title, status, theme, embed shortcode, entry count, and every question with its field id, type, label, required flag and choices. Use it before submitting to a form or interpreting its entries — values in entries are keyed by these field ids, and choice answers store the choice value, not its label.', 'allterrain-forms' ), 'category' => $category, - 'permission_callback' => $read_anything, + 'permission_callback' => $read_form, 'execute_callback' => 'alltfo_ability_get_form', 'input_schema' => array( 'type' => 'object', @@ -280,7 +302,7 @@ function alltfo_register_abilities() { 'label' => __( 'List entries', 'allterrain-forms' ), 'description' => __( 'Queries a form’s submissions. Each entry returns its answers twice: raw values keyed by field id, and a human-readable line per question with the label resolved — use the readable form for summaries and the raw form for computation. Filter by free-text search, date range (after/before, Y-m-d), status (alltfo-unread, alltfo-read, alltfo-spam) and starred; paginate with page/per_page.', 'allterrain-forms' ), 'category' => $category, - 'permission_callback' => $read_entries, + 'permission_callback' => $read_form_entries, 'execute_callback' => 'alltfo_ability_list_entries', 'input_schema' => array( 'type' => 'object', @@ -343,9 +365,9 @@ function alltfo_register_abilities() { 'allterrain-forms/submit-form', array( 'label' => __( 'Submit a form', 'allterrain-forms' ), - 'description' => __( 'Submits answers to a form through the same pipeline a visitor uses: availability, validation, anti-spam, storage, notification emails and confirmations all run. Values are keyed by field id (from get-form); choice fields take the choice value, checkbox groups take an array of them. On validation failure nothing is stored and the per-field errors are returned — fix them and call again.', 'allterrain-forms' ), + 'description' => __( 'Submits answers to a form through the same pipeline a visitor uses: availability, validation, anti-spam, storage, notification emails and confirmations all run. Requires an authenticated user. Values are keyed by field id (from get-form); choice fields take the choice value, checkbox groups take an array of them. On validation failure nothing is stored and the per-field errors are returned — fix them and call again.', 'allterrain-forms' ), 'category' => $category, - 'permission_callback' => '__return_true', + 'permission_callback' => $is_authenticated, 'execute_callback' => 'alltfo_ability_submit_form', 'input_schema' => array( 'type' => 'object', @@ -376,7 +398,7 @@ function alltfo_register_abilities() { 'label' => __( 'Form report', 'allterrain-forms' ), 'description' => __( 'The analytics for one form as structured data: views, starts, submissions, conversion and completion rates, a 90-day timeline, per-question answer distributions, numeric summaries, and a Net Promoter Score panel where a 0–10 question exists. Pass group_by (a choice field id) to break every question down by how a grouping question was answered. Prefer this over fetching every entry when the job is summarising.', 'allterrain-forms' ), 'category' => $category, - 'permission_callback' => $read_entries, + 'permission_callback' => $read_form_entries, 'execute_callback' => 'alltfo_ability_form_report', 'input_schema' => array( 'type' => 'object', @@ -402,7 +424,12 @@ function alltfo_register_abilities() { } /** - * Every form, distilled. + * Every form the caller may see, distilled. + * + * Each form is held against the per-form gate before it is listed: a user the + * `alltfo_can_read_entries` filter confines to a department's forms gets that + * department's list, not the site's. Editors see everything — they could open + * any form in the builder anyway. * * @since 0.3.0 * @@ -420,7 +447,14 @@ function alltfo_ability_list_forms() { ) ); - return array_map( 'alltfo_ability_form', $posts ); + $posts = array_filter( + $posts, + static function ( $post ) { + return alltfo_can_edit_forms() || alltfo_can_read_entries( $post->ID ); + } + ); + + return array_values( array_map( 'alltfo_ability_form', $posts ) ); } /** @@ -581,9 +615,12 @@ function alltfo_ability_get_entry( $input ) { * including a *valid* time-trap signature stamped far enough in the past to * pass the minimum-time check. Minting it here is not defeating the trap: the * trap exists to catch bots that post faster than a human can read, and an - * agent invoking a described ability through an authenticated channel is not - * the traffic it hunts. Every other check — honeypot, rate limit, blocklist, - * Akismet, validation — runs exactly as it does for a visitor. + * agent invoking a described ability is not the traffic it hunts — which is + * exactly why the ability's permission callback requires an authenticated + * user. An anonymous caller never reaches this function; they have the + * rendered form and the public REST route, where the trap runs whole. Every + * other check — honeypot, rate limit, blocklist, Akismet, validation — runs + * exactly as it does for a visitor. * * @since 0.3.0 * diff --git a/includes/actions.php b/includes/actions.php index af6f6c7..e707c41 100644 --- a/includes/actions.php +++ b/includes/actions.php @@ -450,7 +450,12 @@ function alltfo_action_webhook( $settings, $context ) { $headers['X-ATF-Signature'] = 'sha256=' . hash_hmac( 'sha256', $body, (string) $settings['secret'] ); } - $response = wp_remote_post( + // The safe variant, not `wp_remote_post()`: the URL was validated above, + // but only the *first* URL. A webhook endpoint that answers 302 would have + // the followed redirect land wherever it pointed — an internal address + // included — with no second look. `wp_safe_remote_post()` re-validates + // every hop. + $response = wp_safe_remote_post( $url, array( // Short, and blocking. A webhook that hangs would hold the visitor diff --git a/includes/confirmations.php b/includes/confirmations.php index 0ddcb12..f425a9b 100644 --- a/includes/confirmations.php +++ b/includes/confirmations.php @@ -140,7 +140,13 @@ function alltfo_resolve_confirmation( $schema, $values, $entry_id, $form_id ) { ? $chosen['message'] : __( 'Thank you. Your submission has been received.', 'allterrain-forms' ); - $resolved['message'] = alltfo_replace_merge_tags( $message, $context ); + // Kses'd here, at resolution, so both render paths carry the same + // armour: the no-JS path escapes again in `alltfo_success_screen_html()`, + // but the AJAX path hands this string straight to the bundle, which + // injects it as HTML. Field values inside it are already tag-free — + // every field type's sanitiser strips tags — so this is the second + // layer, not the first. + $resolved['message'] = wp_kses_post( alltfo_replace_merge_tags( $message, $context ) ); } else { $resolved['url'] = alltfo_confirmation_url( $chosen, $context ); diff --git a/includes/rest.php b/includes/rest.php index 05060b9..7ba3d59 100644 --- a/includes/rest.php +++ b/includes/rest.php @@ -324,8 +324,9 @@ function alltfo_register_rest_routes() { // began filling this form in" ping comes from a logged-out visitor // on the front end. It records nothing but an anonymous counter // bump against a published form — the handler verifies the form - // exists and has analytics switched on before touching anything — - // and nothing is read back or sent to any external service. + // exists *and is published*, and the recorder refuses forms whose + // analytics are switched off — and nothing is read back or sent to + // any external service. 'permission_callback' => '__return_true', 'args' => array( 'form_id' => array( @@ -492,14 +493,17 @@ function alltfo_rest_track( $request ) { $form_id = absint( $request->get_param( 'form_id' ) ); $event = sanitize_key( (string) $request->get_param( 'event' ) ); - // Only a real form's counter can be bumped: an id pointing at another post - // type, or at nothing, is dropped without side effects, so the public - // route cannot be used to write meta onto arbitrary posts. Status is - // deliberately not checked — the renderer records views for any existing - // form, and the two counters must agree about which forms count. + // Only a published form's counter can be bumped: an id pointing at another + // post type, at a draft, trashed or archived form, or at nothing, is + // dropped without side effects — so the public route can neither write + // meta onto arbitrary posts nor be used to probe or inflate forms that + // are not public. A draft embedded somewhere counts its views (the + // renderer runs as the page does) but not starts from this route; that + // small asymmetry is the price of the unauthenticated surface staying + // confined to what is actually published. $form = $form_id ? get_post( $form_id ) : null; - if ( $form && ALLTFO_FORM_TYPE === $form->post_type && 'start' === $event ) { + if ( $form && ALLTFO_FORM_TYPE === $form->post_type && 'publish' === $form->post_status && 'start' === $event ) { alltfo_record_start( $form_id ); } diff --git a/includes/schema.php b/includes/schema.php index 924340c..f0b332b 100644 --- a/includes/schema.php +++ b/includes/schema.php @@ -38,6 +38,11 @@ * stored but the retention default is 0 (keep forever) -- a plugin should not * quietly delete somebody's data on a schedule they never chose. * + * Akismet is **off**, even on a site that has it installed and configured. + * Switching it on sends each submission -- the answers, the sender's IP and + * user agent -- to Akismet's servers, and data leaving the site is a decision + * the site owner makes per form, never a default a plugin makes for them. + * * @since 0.1.0 * * @return array A complete schema with no fields. @@ -71,7 +76,7 @@ function alltfo_default_schema() { 'timeTrap' => 3, 'rateLimit' => 10, 'blocklist' => '', - 'akismet' => true, + 'akismet' => false, 'challenge' => false, ), 'storage' => array( diff --git a/includes/spam.php b/includes/spam.php index 2533073..d78a751 100644 --- a/includes/spam.php +++ b/includes/spam.php @@ -3,9 +3,10 @@ * Spam screening without a captcha. * * Five checks, none of which asks the visitor to do anything: a honeypot, a time - * trap, a per-IP rate limit, a word blocklist, and Akismet when the site already - * has it. An optional arithmetic challenge exists for forms under sustained - * attack, and is off by default. + * trap, a per-IP rate limit, a word blocklist, and Akismet — off by default, + * because it sends the submission off-site, and that is the site owner's call + * to make per form. An optional arithmetic challenge exists for forms under + * sustained attack, and is off by default too. * * No reCAPTCHA, no hCaptcha, no "select all the traffic lights". Those work, but * they charge the visitor for the site's spam problem, they are a WCAG failure diff --git a/includes/submission.php b/includes/submission.php index a186c04..c65bf1c 100644 --- a/includes/submission.php +++ b/includes/submission.php @@ -35,7 +35,9 @@ * @since 0.1.0 * * @param int $form_id The form. - * @param array $request The raw request body, unslashed. + * @param array $request The request body, unslashed. Deep-sanitised on entry + * via `alltfo_sanitize_request()`, so callers may hand + * it over raw. * @param array $files The `$_FILES` array. * @return array { * The result. @@ -60,13 +62,21 @@ function alltfo_process_submission( $form_id, $request, $files = array() ) { $schema = alltfo_get_form_schema( $form_id ); + // Sanitise early: the whole request is cleaned here, before any code — + // including the `alltfo_before_submission` action below — reads a byte of + // it. This is the coarse pass; each answer is then sanitised *again* + // through its own field type in `alltfo_sanitize_submission()`, which + // knows the shape it expects. + $request = alltfo_sanitize_request( $request ); + /** * Fires before a submission is processed. * * @since 0.1.0 + * @since 0.4.0 `$request` arrives deep-sanitised via `alltfo_sanitize_request()`. * * @param int $form_id The form. - * @param array $request The raw request. + * @param array $request The request, deep-sanitised. * @param array $schema The form schema. */ do_action( 'alltfo_before_submission', $form_id, $request, $schema ); @@ -206,6 +216,48 @@ function alltfo_process_submission( $form_id, $request, $files = array() ) { ); } +/** + * Deep-sanitises a raw request body, early. + * + * The coarse first pass over everything a request carries, run at the top of + * the pipeline so nothing downstream ever holds dirty data: keys become plain + * text, every scalar goes through `sanitize_textarea_field()` — tags and + * control characters stripped, the newlines a message field exists to collect + * kept — and anything that is neither an array nor a scalar is dropped. Each + * answer is then sanitised *again* through its own field type in + * `alltfo_sanitize_submission()`; this pass is about never trusting the wire, + * that one about the shape each field expects. + * + * The depth cap matches nothing a real form produces — the deepest legitimate + * shape, a value inside a repeater row, sits four levels under `atf` — and + * exists so a hand-crafted JSON body cannot turn the recursion into work. + * + * @since 0.4.0 + * + * @param mixed $request The raw request body, unslashed. + * @param int $depth Internal recursion depth. + * @return array The sanitised request. + */ +function alltfo_sanitize_request( $request, $depth = 0 ) { + if ( ! is_array( $request ) || $depth > 8 ) { + return array(); + } + + $clean = array(); + + foreach ( $request as $key => $value ) { + $key = is_int( $key ) ? $key : sanitize_text_field( (string) $key ); + + if ( is_array( $value ) ) { + $clean[ $key ] = alltfo_sanitize_request( $value, $depth + 1 ); + } elseif ( null === $value || is_scalar( $value ) ) { + $clean[ $key ] = sanitize_textarea_field( (string) $value ); + } + } + + return $clean; +} + /** * Sanitises every submitted value through its own field type. * @@ -502,7 +554,7 @@ function alltfo_handle_post_submission() { return; } - $request = wp_unslash( $_POST ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Every value is sanitised through its own field type in `alltfo_sanitize_submission()`. + $request = alltfo_sanitize_request( wp_unslash( $_POST ) ); $files = isset( $_FILES ) ? $_FILES : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated in full by `alltfo_handle_uploads()`. $result = alltfo_process_submission( $form_id, $request, $files ); diff --git a/phpcs.xml.dist b/phpcs.xml.dist index a4abe23..25f006d 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -45,6 +45,14 @@ + + + + + + + + diff --git a/readme.txt b/readme.txt index e4a54a9..69b1dd8 100644 --- a/readme.txt +++ b/readme.txt @@ -122,9 +122,10 @@ pick it on any form, export it as JSON, import it on another site. = Spam protection without a CAPTCHA = A honeypot, a signed time trap, a per-address rate limit, a word blocklist, -and Akismet if you already have it. Nothing asks a visitor to identify traffic -lights, because a CAPTCHA charges the visitor for the site's spam problem and -fails accessibility for a good number of people. +and — if you already have it and switch it on for a form — Akismet. Nothing +asks a visitor to identify traffic lights, because a CAPTCHA charges the +visitor for the site's spam problem and fails accessibility for a good number +of people. Anything judged spam is **kept**, in a spam folder, never silently thrown away. @@ -195,12 +196,13 @@ The plugin makes no connection of its own to us or to anyone else — no accounts, no license checks, no telemetry, no updates outside WordPress.org. Two optional features send data to a destination **you** choose: -* **Akismet** — if the [Akismet plugin](https://wordpress.org/plugins/akismet/) - is installed and configured on your site *and* you switch it on for a form, - each submission to that form (the answers, plus the sender's IP and - user agent) is sent to Akismet's servers for a spam verdict, and spam/ham - corrections you make in the entries screen are reported back. Without the - Akismet plugin, or with the per-form switch off, nothing is sent. See the +* **Akismet** — off by default on every form, even when the + [Akismet plugin](https://wordpress.org/plugins/akismet/) is installed and + configured on your site. Only when you switch it on for a form is each + submission to that form (the answers, plus the sender's IP and user agent) + sent to Akismet's servers for a spam verdict, with spam/ham corrections you + make in the entries screen reported back. Without the Akismet plugin, or + with the per-form switch off, nothing is sent. See the [Akismet Terms of Service](https://akismet.com/tos/) and the [Automattic Privacy Policy](https://automattic.com/privacy/). * **Webhooks** — a form can POST each entry as JSON to a URL you enter, diff --git a/src/builder.ts b/src/builder.ts index c23b2ce..a0ee001 100644 --- a/src/builder.ts +++ b/src/builder.ts @@ -5043,9 +5043,16 @@ export class Builder { textArea( settings.spam.blocklist, ( value ) => set( 'spam.blocklist', value ), 4 ), 'One per line.' ), - checkbox( 'Use Akismet when it is installed', settings.spam.akismet, ( value ) => + checkbox( 'Check submissions with Akismet', settings.spam.akismet, ( value ) => set( 'spam.akismet', value ) ), + el( 'p', { + class: 'atfb-hint', + text: + 'Off unless you switch it on. When on, each submission — the answers, plus the ' + + 'sender’s IP address and user agent — is sent to Akismet’s servers for a spam ' + + 'verdict. Needs the Akismet plugin installed and configured.', + } ), checkbox( 'Ask a simple sum before sending', settings.spam.challenge, ( value ) => set( 'spam.challenge', value ) ), diff --git a/tests/phpunit/tests/abilities.php b/tests/phpunit/tests/abilities.php index 688c694..ff26cb3 100644 --- a/tests/phpunit/tests/abilities.php +++ b/tests/phpunit/tests/abilities.php @@ -288,4 +288,103 @@ public function test_subscriber_is_refused() { $this->assertWPError( $create ); $this->assertWPError( $read ); } + + /** + * An unauthenticated caller is refused everywhere — submit-form included. + * + * Submitting mints a valid time-trap signature, a liberty that is only + * honest on a channel that identifies its caller. Anonymous traffic has + * the rendered form and the public REST route, where the trap runs whole. + * + * @covers ::alltfo_register_abilities + */ + public function test_logged_out_caller_is_refused() { + $form_id = $this->make_form(); + $form = $this->run_ability( 'allterrain-forms/get-form', array( 'form_id' => $form_id ) ); + $name_id = $form['fields'][0]['id']; + + wp_set_current_user( 0 ); + + $this->assertWPError( wp_get_ability( 'allterrain-forms/get-form' )->execute( array( 'form_id' => $form_id ) ) ); + $this->assertWPError( wp_get_ability( 'allterrain-forms/form-report' )->execute( array( 'form_id' => $form_id ) ) ); + + $refused = wp_get_ability( 'allterrain-forms/submit-form' )->execute( + array( + 'form_id' => $form_id, + 'values' => array( $name_id => 'Anonymous' ), + ) + ); + + $this->assertWPError( $refused ); + $this->assertSame( 0, alltfo_count_entries( $form_id ), 'The refused submit stored nothing.' ); + } + + /** + * Any authenticated user may submit — submitting is what visitors do. + * + * @covers ::alltfo_ability_submit_form + */ + public function test_a_subscriber_can_submit() { + $form_id = $this->make_form(); + $form = $this->run_ability( 'allterrain-forms/get-form', array( 'form_id' => $form_id ) ); + $name_id = $form['fields'][0]['id']; + + wp_set_current_user( self::factory()->user->create( array( 'role' => 'subscriber' ) ) ); + + $accepted = wp_get_ability( 'allterrain-forms/submit-form' )->execute( + array( + 'form_id' => $form_id, + 'values' => array( $name_id => 'Signed-in visitor' ), + ) + ); + + $this->assertTrue( $accepted['success'] ); + $this->assertSame( 1, alltfo_count_entries( $form_id ) ); + } + + /** + * The per-form filter confines every reading ability, id in hand. + * + * A user the `alltfo_can_read_entries` filter restricts to one form gets + * that form and no other — from get-form, from form-report, and from the + * list itself. + * + * @covers ::alltfo_register_abilities + * @covers ::alltfo_ability_list_forms + */ + public function test_per_form_filter_confines_the_read_abilities() { + $allowed = $this->make_form(); + $other = $this->make_form(); + + $reader = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + + ( new WP_User( $reader ) )->add_cap( 'alltfo_read_entries' ); + wp_set_current_user( $reader ); + + $filter = static function ( $can, $form_id ) use ( $allowed ) { + // 0 is the "any form at all" question the list gate asks; every + // concrete id except the allowed one is refused. + return ( 0 === $form_id || $allowed === $form_id ) ? $can : false; + }; + + add_filter( 'alltfo_can_read_entries', $filter, 10, 2 ); + + $mine = wp_get_ability( 'allterrain-forms/get-form' )->execute( array( 'form_id' => $allowed ) ); + $theirs = wp_get_ability( 'allterrain-forms/get-form' )->execute( array( 'form_id' => $other ) ); + + $this->assertIsArray( $mine ); + $this->assertWPError( $theirs, 'The schema of a form outside the filter is not readable.' ); + + $this->assertWPError( + wp_get_ability( 'allterrain-forms/form-report' )->execute( array( 'form_id' => $other ) ), + 'The analytics of a form outside the filter are not readable.' + ); + + $ids = wp_list_pluck( wp_get_ability( 'allterrain-forms/list-forms' )->execute(), 'id' ); + + $this->assertContains( $allowed, $ids ); + $this->assertNotContains( $other, $ids, 'The list is confined the same way.' ); + + remove_filter( 'alltfo_can_read_entries', $filter, 10 ); + } } diff --git a/tests/phpunit/tests/actions.php b/tests/phpunit/tests/actions.php new file mode 100644 index 0000000..614134f --- /dev/null +++ b/tests/phpunit/tests/actions.php @@ -0,0 +1,88 @@ + array( + array( + 'id' => 'f1', + 'type' => 'text', + 'label' => 'Name', + ), + ), + ) + ); + + $seen = null; + + $capture = static function ( $pre, $args, $url ) use ( &$seen ) { + $seen = array( + 'args' => $args, + 'url' => $url, + ); + + return array( + 'headers' => array(), + 'body' => '', + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + ); + }; + + add_filter( 'pre_http_request', $capture, 10, 3 ); + + // The site's own host, because the tests container has no DNS and + // `wp_http_validate_url()` refuses a host it cannot resolve — the + // same-host case is the one it accepts without asking a resolver. + $hook_url = home_url( '/hook' ); + + $result = alltfo_action_webhook( + array( + 'url' => $hook_url, + 'secret' => 'a-shared-secret', + ), + array( + 'schema' => alltfo_get_form_schema( $form_id ), + 'values' => array( 'f1' => 'Ada' ), + 'form_id' => $form_id, + 'entry_id' => 0, + ) + ); + + remove_filter( 'pre_http_request', $capture, 10 ); + + $this->assertNotWPError( $result ); + $this->assertIsArray( $seen, 'The request was attempted.' ); + $this->assertSame( $hook_url, $seen['url'] ); + $this->assertTrue( ! empty( $seen['args']['reject_unsafe_urls'] ), 'Every hop of a webhook request is validated, not just the first URL.' ); + $this->assertArrayHasKey( 'X-ATF-Signature', $seen['args']['headers'], 'A configured secret signs the payload.' ); + } +} diff --git a/tests/phpunit/tests/analytics.php b/tests/phpunit/tests/analytics.php index 047d9bd..ce298be 100644 --- a/tests/phpunit/tests/analytics.php +++ b/tests/phpunit/tests/analytics.php @@ -749,6 +749,50 @@ public function test_analytics_switches_gate_the_recording() { unset( $_SERVER['HTTP_USER_AGENT'] ); } + /** + * The public track route bumps published forms and nothing else. + * + * The route is unauthenticated on purpose — the ping comes from a + * logged-out visitor — so what it can touch has to be exactly what is + * public: a draft, trashed or archived form must not be countable, or the + * route becomes a way to probe and inflate forms nobody can see. + * + * @covers ::alltfo_rest_track + */ + public function test_track_route_is_confined_to_published_forms() { + $published = alltfo_test_form(); + $draft = alltfo_test_form(); + $trashed = alltfo_test_form(); + + wp_update_post( + array( + 'ID' => $draft, + 'post_status' => 'draft', + ) + ); + wp_update_post( + array( + 'ID' => $trashed, + 'post_status' => 'trash', + ) + ); + + foreach ( array( $published, $draft, $trashed ) as $form_id ) { + $request = new WP_REST_Request( 'POST', '/' . ALLTFO_REST_NAMESPACE . '/track' ); + + $request->set_param( 'form_id', $form_id ); + $request->set_param( 'event', 'start' ); + + $response = rest_do_request( $request ); + + $this->assertSame( 200, $response->get_status(), 'The route answers 200 regardless — it never confirms what exists.' ); + } + + $this->assertSame( 1, alltfo_get_stats( $published )['starts'] ); + $this->assertSame( 0, alltfo_get_stats( $draft )['starts'], 'A draft form is not countable from the public route.' ); + $this->assertSame( 0, alltfo_get_stats( $trashed )['starts'], 'A trashed form is not countable from the public route.' ); + } + /** * The tally filter is the per-request veto a consent plugin needs. * diff --git a/tests/phpunit/tests/schema.php b/tests/phpunit/tests/schema.php index e30041d..5a1a6f1 100644 --- a/tests/phpunit/tests/schema.php +++ b/tests/phpunit/tests/schema.php @@ -194,6 +194,21 @@ public function test_partial_settings_keep_siblings() { $this->assertSame( 3, $schema['settings']['spam']['timeTrap'], 'A sibling setting was lost in the merge.' ); } + /** + * Akismet is opt-in: a new form must not send submissions off-site by default. + * + * Sending each submission -- answers, IP, user agent -- to Akismet's servers + * is the site owner's decision per form, never a default. A regression here + * is a privacy bug, not a preference. + * + * @covers ::alltfo_default_schema + * @covers ::alltfo_normalize_settings + */ + public function test_akismet_is_off_by_default() { + $this->assertFalse( alltfo_default_schema()['settings']['spam']['akismet'] ); + $this->assertFalse( alltfo_normalize_schema( array() )['settings']['spam']['akismet'], 'A schema that never mentions Akismet stays off.' ); + } + /** * A setting is the type its default declares, whatever the import sent. * diff --git a/tests/phpunit/tests/submission.php b/tests/phpunit/tests/submission.php index 05b082e..9080412 100644 --- a/tests/phpunit/tests/submission.php +++ b/tests/phpunit/tests/submission.php @@ -104,6 +104,51 @@ public function test_a_good_submission_is_stored() { $this->assertSame( 'ada@example.com', $values['f2'] ); } + /** + * The request is deep-sanitised before the first hook can read it. + * + * `alltfo_before_submission` is the earliest reader a request has, so what + * it receives is what "sanitise early" means in practice: no tag survives + * to that point, in a value or in a key. The per-field-type sanitisation + * still runs after this pass. + * + * @covers ::alltfo_sanitize_request + * @covers ::alltfo_process_submission + */ + public function test_request_is_sanitised_before_the_first_hook() { + $seen = null; + + add_action( + 'alltfo_before_submission', + static function ( $form_id, $request ) use ( &$seen ) { + $seen = $request; + }, + 10, + 2 + ); + + $result = alltfo_process_submission( + $this->form_id, + $this->request( + array( + 'f1' => 'Ada', + 'evilkey' => 'never a field', + ) + ) + ); + + $this->assertIsArray( $seen, 'The action fired.' ); + $this->assertSame( 'Ada', $seen['atf']['f1'], 'The script tag and its payload are gone before the first hook.' ); + $this->assertArrayHasKey( 'evilkey', $seen['atf'], 'Keys are sanitised too.' ); + $this->assertArrayNotHasKey( 'evilkey', $seen['atf'] ); + + $this->assertTrue( $result['success'] ); + + $values = json_decode( get_post_meta( $result['entry_id'], ALLTFO_META_VALUES, true ), true ); + + $this->assertSame( 'Ada', $values['f1'] ); + } + /** * A submission missing a required field is refused, and nothing is stored. * @@ -983,6 +1028,62 @@ public function test_default_confirmation() { $this->assertStringContainsString( 'Thank you', $result['confirmation']['message'] ); } + /** + * A hostile answer cannot ride a merge tag into the confirmation as markup. + * + * The confirmation message travels to the bundle, which injects it as HTML + * — so this is the surface a script-in-an-answer would have to cross. It + * is blocked twice: every field type's sanitiser strips tags from the + * value, and the resolved message is kses'd whole. + * + * @covers ::alltfo_resolve_confirmation + * @covers ::alltfo_sanitize_field_value + */ + public function test_confirmation_merge_tag_cannot_carry_script() { + $form_id = alltfo_test_form( + array( + 'fields' => array( + array( + 'id' => 'f1', + 'type' => 'text', + 'label' => 'Name', + ), + ), + 'confirmations' => array( + array( + 'id' => 'c1', + 'enabled' => true, + 'type' => 'message', + 'message' => 'Thanks {field:f1}!', + ), + ), + ) + ); + + $issued = time() - 30; + + $result = alltfo_process_submission( + $form_id, + array( + 'alltfo_form_id' => $form_id, + 'alltfo_t' => $issued, + 'alltfo_ts' => alltfo_sign_timestamp( $form_id, $issued ), + 'atf' => array( + 'f1' => 'Ada', + ), + ) + ); + + $this->assertTrue( $result['success'] ); + + $message = $result['confirmation']['message']; + + $this->assertStringContainsString( 'Ada', $message ); + $this->assertStringNotContainsString( 'assertStringNotContainsString( 'onerror', $message ); + $this->assertStringNotContainsString( 'alert', $message, 'The script payload is gone entirely, not merely defanged.' ); + } + /** * A redirect query only carries scalar values. *