diff --git a/assets/css/builder.css b/assets/css/builder.css index b5dda6c..32e1c31 100644 --- a/assets/css/builder.css +++ b/assets/css/builder.css @@ -2942,3 +2942,130 @@ os-checkbox-label.atfb-check { font-style: italic; color: var( --atfb-muted ); } + +/* -------------------------------------------------------------------------- */ +/* The repeater as a container on the canvas */ +/* -------------------------------------------------------------------------- */ + +/* The frame borrows the front end's own row-card classes for its border and + background, so the builder shows the card the visitor will get; everything + prefixed atfb- is builder chrome on top of it. */ + +.atfb-repeater { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 18px; + margin-block-start: 8px; +} + +.atfb-repeater__frame { + inline-size: 100%; +} + +.atfb-repeater__title { + display: inline-flex; + gap: 5px; + align-items: baseline; +} + +.atfb-repeater__item-label { + min-inline-size: 3ch; +} + +.atfb-repeater__ordinal { + opacity: 0.55; +} + +/* The zone is where sub-fields live and where drags land. Padded even when + empty so there is always something to aim a drop at. */ +.atfb-repeater__zone { + display: flex; + flex-direction: column; + gap: 10px; + padding: 14px; + min-block-size: 56px; + border-radius: 0 0 var( --atf-radius-field, 8px ) var( --atf-radius-field, 8px ); +} + +.atfb-repeater__zone.is-dropping { + background: var( --atfb-accent-soft ); + box-shadow: inset 0 0 0 2px var( --atfb-accent-line ); +} + +.atfb-repeater__empty { + margin: 0; + padding: 14px; + border: 1px dashed var( --atfb-border-strong ); + border-radius: var( --atfb-radius ); + font-size: 12px; + color: var( --atfb-muted ); + text-align: center; +} + +/* One sub-field: a smaller card with its own head, selection ring and grab + cursor, holding a full live preview of the control. */ +.atfb-subcard { + border: 1px solid var( --atfb-border ); + border-radius: var( --atfb-radius ); + background: var( --atfb-bg ); + cursor: grab; + touch-action: none; +} + +.atfb-subcard:hover { + border-color: var( --atfb-accent ); +} + +.atfb-subcard.is-selected { + border-color: var( --atfb-accent ); + box-shadow: 0 0 0 2px var( --atfb-accent-soft ); +} + +.atfb-subcard:focus-visible { + outline: 2px solid var( --atfb-accent ); + outline-offset: 2px; +} + +.atfb-subcard__head { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + border-block-end: 1px solid var( --atfb-border ); +} + +.atfb-subcard__type { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.02em; + color: var( --atfb-muted ); +} + +.atfb-subcard__required { + font-size: 11px; + color: var( --atfb-warning ); +} + +.atfb-subcard__head .atfb-preview__remove { + margin-inline-start: auto; + opacity: 1; +} + +.atfb-subcard .atfb-preview { + padding: 10px 12px; +} + +/* The editable Add-another button sits under the frame with the same breathing + room the front end gives it. */ +.atfb-repeater > .atfb-repeater__add { + margin-block-start: 0; +} + +/* The crumb that says a field lives inside a repeater. */ +.atfb-inspector__crumb { + padding: 6px 8px; + border-radius: 4px; + background: var( --atfb-well ); +} diff --git a/assets/css/form.css b/assets/css/form.css index 212cc78..16ba177 100644 --- a/assets/css/form.css +++ b/assets/css/form.css @@ -912,22 +912,41 @@ .atf-repeater__rows { display: grid; - gap: 12px; + gap: 14px; margin-block-start: var( --atf-gap-label ); } +/* Each row is a card: a named thing — "Attendee 1" — rather than an anonymous + stripe of boxes, because the whole point of a repeater is that one row is one + *thing* the visitor is describing. */ .atf-repeater__row { - position: relative; - display: grid; - grid-template-columns: repeat( 12, 1fr ); - gap: 12px; - padding: 14px; - padding-inline-end: 44px; border: var( --atf-border-width ) var( --atf-border-style ) var( --atf-border ); border-radius: var( --atf-radius-field ); background: var( --atf-surface-alt ); } +.atf-repeater__row-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 10px 16px; + border-block-end: var( --atf-border-width ) var( --atf-border-style ) var( --atf-border ); +} + +.atf-repeater__title { + font-size: var( --atf-size-label ); + font-weight: var( --atf-weight-label ); + color: var( --atf-heading ); +} + +.atf-repeater__row-grid { + display: grid; + grid-template-columns: repeat( 12, 1fr ); + gap: 14px; + padding: 16px; +} + .atf-repeater__field { grid-column: span 12; } @@ -940,12 +959,17 @@ grid-column: span 4; } +@container atf-form ( max-width: 480px ) { + .atf-repeater__field.atf-field--half, + .atf-repeater__field.atf-field--third { + grid-column: span 12; + } +} + .atf-repeater__remove { - position: absolute; - inset-block-start: 8px; - inset-inline-end: 8px; inline-size: 28px; block-size: 28px; + flex: none; display: grid; place-items: center; border: 0; @@ -962,6 +986,14 @@ color: var( --atf-error ); } +/* Breathing room the button was missing: it sits under the row cards, not + glued to them, and never stretches to the form's full width — themes whose + `--atf-button-width` is 100% mean the submit button, not this one. */ +.atf-repeater__add { + margin-block-start: 18px; + inline-size: auto; +} + /* -------------------------------------------------------------------------- */ /* Layout fields */ /* -------------------------------------------------------------------------- */ diff --git a/assets/js/analytics.js b/assets/js/analytics.js index f421256..74a1b2e 100644 --- a/assets/js/analytics.js +++ b/assets/js/analytics.js @@ -722,7 +722,9 @@ var allTerrainFormsAnalytics = function(exports) { parts.push( el("p", { class: "atfa-question__note", - text: `${field.answered} of ${sampled} people answered this.` + // A repeater sub-field is answered per row, not per + // person, and says so through `of` and `unit`. + text: `${field.answered} of ${field.of ?? sampled} ${field.unit ?? "people"} answered this.` }) ); } @@ -734,7 +736,7 @@ var allTerrainFormsAnalytics = function(exports) { if (field.numbers) { bits.push(`mean ${field.numbers.mean}`, `median ${field.numbers.median}`); } - bits.push(`${field.answered}/${sampled}`); + bits.push(`${field.answered}/${field.of ?? sampled}`); return bits.join(" · "); } /** diff --git a/assets/js/analytics.min.js b/assets/js/analytics.min.js index 3d04f21..59a263d 100644 --- a/assets/js/analytics.min.js +++ b/assets/js/analytics.min.js @@ -1 +1 @@ -var allTerrainFormsAnalytics=function(p){"use strict";function F(){return window.wp?.os??null}const c=window.allTerrainForms;class y extends Error{constructor(e,t,n=""){super(e),this.name="ApiError",this.status=t,this.code=n}}function T(s,e){return s.includes("?")?`${s}${e.replace("?","&")}`:`${s}${e}`}async function M(s,e={}){if(!c?.restUrl)throw new y("AllTerrain Forms is not configured on this page.",0);const t=T(c.restUrl,s),n={"Content-Type":"application/json",...e.headers??{}};c.nonce&&(n["X-WP-Nonce"]=c.nonce);const o=F(),r=await(o?.fetch?(l,m)=>o.fetch(l,m,{source:"allterrain-forms"}):(l,m)=>fetch(l,m))(t,{credentials:"same-origin",...e,headers:n});if(!r.ok){let l=`Request failed with status ${r.status}.`,m="";try{const h=await r.json();l=h.message??l,m=h.code??""}catch{}throw new y(l,r.status,m)}if(r.status!==204)return await r.json()}const d=s=>M(s);async function D(s){if(!c?.wpRestUrl)throw new y("AllTerrain Forms is not configured on this page.",0);const e=c.nonce?{"X-WP-Nonce":c.nonce}:{},t=F(),n=T(c.wpRestUrl,s),o=t?.fetch?await t.fetch(n,{credentials:"same-origin",headers:e},{source:"allterrain-forms"}):await fetch(n,{credentials:"same-origin",headers:e});if(!o.ok)throw new y(`Request failed with status ${o.status}.`,o.status);return await o.json()}function j(s){if(!s||!s.includes("&"))return s;const e=document.createElement("textarea");return e.innerHTML=s,e.value}const u=(s,e)=>M(s,{method:"POST",body:JSON.stringify(e)}),w=s=>M(s,{method:"DELETE"});function C(s){const e=new URLSearchParams;for(const[n,o]of Object.entries(s))o===void 0||o===""||o===!1||e.set(n,String(o));const t=e.toString();return t?`?${t}`:""}function g(s){const e=s?.schema?.settings;return e&&(!e.themeOverrides||Array.isArray(e.themeOverrides))&&(e.themeOverrides={...e.themeOverrides}),s}const f={config:()=>d("/config"),listForms:()=>d("/forms"),getForm:s=>d(`/forms/${s}`).then(g),createForm:s=>u("/forms",s).then(g),updateForm:(s,e)=>u(`/forms/${s}`,e).then(g),duplicateForm:s=>u(`/forms/${s}/duplicate`,{}).then(g),deleteForm:s=>w(`/forms/${s}`),preview:(s,e)=>u(`/forms/${s}/preview`,e),pages:()=>D("wp/v2/pages?per_page=100&status=publish&orderby=title&order=asc&_fields=id,title").then(s=>s.map(e=>({id:e.id,title:j(e.title?.rendered??"")||`#${e.id}`}))),mergeTags:s=>d(`/forms/${s}/merge-tags`).then(e=>e.groups),analytics:(s,e="")=>d(`/forms/${s}/analytics${e?`?dimension=${encodeURIComponent(e)}`:""}`),demoStatus:()=>d("/demo"),demoSeed:s=>u("/demo",s?{count:s}:{}),demoRemove:()=>w("/demo"),listEntries:s=>d(`/entries${C(s)}`),getEntry:s=>d(`/entries/${s}`),updateEntry:(s,e)=>u(`/entries/${s}`,e),deleteEntry:s=>w(`/entries/${s}`),exportEntries:s=>d(`/entries/export${C(s)}`),listThemes:()=>d("/themes"),saveTheme:s=>u("/themes",s),deleteTheme:s=>w(`/themes/${s}`)};function P(s){return`allterrain-forms/requested-form-${s}`}function R(s){try{const e=window.sessionStorage.getItem(P(s));return window.sessionStorage.removeItem(P(s)),Number(e)||0}catch{return 0}}function a(s,e={}){const t=document.createElement(s);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[n,o]of Object.entries(e.attrs??{}))o===void 0||o===!1||t.setAttribute(n,o===!0?"":String(o));Object.assign(t.style,e.style??{});for(const[n,o]of Object.entries(e.on??{}))t.addEventListener(n,o);for(const n of e.children??[])n==null||n===!1||t.append(n);return t}function _(s){s.replaceChildren()}function N(s,e,t){if(E("os-select")&&E("os-option")){const n=document.createElement("os-select");n.setAttribute("value",s),n.classList.add("atfb-field");for(const o of e){const i=document.createElement("os-option");i.setAttribute("value",o.value),i.textContent=o.label,n.append(i)}return n.addEventListener("os-pick",o=>{t(String(o.detail?.value??""))}),n}return a("select",{class:"atfb-input atfb-select",on:{change:n=>t(n.target.value)},children:e.map(n=>a("option",{value:n.value,text:n.label,attrs:{selected:n.value===s}}))})}function I(s,e,t="secondary",n){const o=[null,a("span",{text:s})];if(E("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 r of o)r&&i.append(r);return i}return a("button",{class:`atfb-button atfb-button--${t}`,type:"button",on:{click:e},children:o})}function E(s){return typeof customElements<"u"&&!!customElements.get(s)}const B=["os-button","os-checkbox-label","os-number-field","os-select","os-option","os-segmented","os-segment","os-color-field","os-empty-state"];let $=null;function U(){if($)return $;const s=window.wp?.os;return $=s?.loadComponents?s.loadComponents(B).catch(()=>{}):Promise.resolve(),$}async function G(s,e=""){const t=window.wp?.os;return t?.confirm?t.confirm({title:e,message:s,danger:!0}):window.confirm(s)}function x(s,e="",t="info"){const n=window.wp?.os;if(n?.notify){n.notify({title:s,body:e,type:t});return}console.info(`[AllTerrain Forms] ${s}${e?`: ${e}`:""}`)}function W(s){const e=s.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)}const O=[{key:"detractors",label:"Detractors",hint:"0–6"},{key:"passives",label:"Passives",hint:"7–8"},{key:"promoters",label:"Promoters",hint:"9–10"}],q=50;function L(s){const e=Math.floor(s.min),t=Math.ceil(s.max);if(!Number.isFinite(e)||!Number.isFinite(t)||tMath.min(i-1,Math.max(0,Math.floor((h-e)/o))),l=[];for(let h=0;ht.id===e)?this.formId=e:this.formId||(this.formId=this.forms[0].id),await this.load()}async showForm(e){this.formId=e,this.dimension="",await this.load()}async load(){try{this.report=await f.analytics(this.formId,this.dimension)}catch{this.fail("Could not load that report.");return}this.dimension=this.report.breakdown?.id??"",this.renderBar(),this.render()}fail(e){_(this.bar),_(this.body),this.body.append(a("p",{class:"atfa__empty",text:e}))}renderBar(){_(this.bar),this.bar.append(N(String(this.formId),this.forms.map(t=>({value:String(t.id),label:t.title||`#${t.id}`})),t=>{this.formId=Number(t),this.dimension="",this.load()}));const e=this.report?.dimensions??[];e.length>1&&this.bar.append(a("span",{class:"atfa__bar-label",text:"Group by"}),N(this.dimension,e.map(t=>({value:t.id,label:t.label})),t=>{this.dimension=t,this.load()}))}render(){const e=this.report;if(_(this.body),!!e){if(this.body.append(this.kpis(e)),e.submissions<1&&e.sampled<1)this.body.append(a("p",{class:"atfa__empty",text:"No submissions yet. Everything below fills in as they arrive."}));else{this.body.append(this.timeline(e));for(const t of e.fields)t.nps&&this.body.append(this.npsPanel(t));e.breakdown&&e.breakdown.groups.length&&this.body.append(this.breakdownPanel(e.breakdown)),this.body.append(this.questions(e))}this.demoAvailable&&this.body.append(this.demoPanel())}}kpis(e){const t=[{label:"Submissions",value:String(e.submissions)},{label:"Views",value:String(e.views)},{label:"Conversion",value:`${e.conversion}%`,hint:"of people who saw it"},{label:"Completion",value:`${e.completion}%`,hint:"of people who started"},{label:"Unread",value:String(e.unread)},{label:"Spam",value:String(e.spam)}];return a("div",{class:"atfa-kpis",children:t.map(n=>a("div",{class:"atfa-kpi",children:[a("span",{class:"atfa-kpi__value",text:n.value}),a("span",{class:"atfa-kpi__label",text:n.label}),n.hint?a("span",{class:"atfa-kpi__hint",text:n.hint}):null]}))})}timeline(e){const t=e.timeline,n=t.reduce((i,r)=>Math.max(i,r.count),0),o=t.reduce((i,r)=>i+r.count,0);return this.panel("When they answered",`${o} in the last ${t.length} days · busiest day ${n}`,[a("div",{class:"atfa-timeline",children:t.map(i=>a("div",{class:`atfa-timeline__day${i.count?"":" is-empty"}`,attrs:{style:`height:${n?Math.max(2,i.count/n*100):0}%`,title:`${i.date}: ${i.count}`}}))}),a("div",{class:"atfa-timeline__axis",children:[a("span",{text:t[0]?.date??""}),a("span",{text:t[t.length-1]?.date??""})]})])}npsPanel(e){const t=e.nps;if(!t)return a("div");const n=o=>t.responses?o/t.responses*100:0;return this.panel(e.label,`${t.responses} answers · Net Promoter Score`,[a("div",{class:"atfa-nps",children:[a("div",{class:`atfa-nps__score is-${t.score>=50?"great":t.score>=0?"ok":"poor"}`,children:[a("span",{class:"atfa-nps__number",text:`${t.score>0?"+":""}${t.score}`}),a("span",{class:"atfa-nps__caption",text:"NPS"})]}),a("div",{class:"atfa-nps__detail",children:[a("div",{class:"atfa-nps__bar",children:O.map(o=>a("div",{class:`atfa-nps__band is-${o.key}`,attrs:{style:`width:${n(t[o.key])}%`,title:`${o.label} (${o.hint}): ${t[o.key]}`}}))}),a("div",{class:"atfa-nps__legend",children:O.map(o=>a("span",{class:`atfa-nps__key is-${o.key}`,children:[a("strong",{text:String(t[o.key])}),a("span",{text:` ${o.label} `}),a("small",{text:o.hint})]}))})]})]})])}breakdownPanel(e){const t=e.groups[0]?.metrics??[];if(!t.length)return a("div");const n=new Map;for(const r of e.groups)for(const l of r.metrics)n.set(l.id,Math.max(n.get(l.id)??0,l.mean));const o=a("tr",{children:[a("th",{text:e.label,attrs:{scope:"col"}}),a("th",{text:"Answers",attrs:{scope:"col"}}),...t.map(r=>a("th",{text:r.label,attrs:{scope:"col"}}))]}),i=e.groups.map(r=>a("tr",{children:[a("th",{class:"atfa-table__label",text:r.label,attrs:{scope:"row"}}),a("td",{class:"atfa-table__count",text:String(r.count)}),...r.metrics.map(l=>a("td",{children:[a("div",{class:"atfa-meter",children:[a("div",{class:"atfa-meter__fill",attrs:{style:`width:${l.mean/(n.get(l.id)||1)*100}%`}})]}),a("span",{class:"atfa-meter__value",text:l.nps!==null?`${l.mean.toFixed(1)} · NPS ${l.nps>0?"+":""}${l.nps}`:l.mean.toFixed(2)})]}))]}));return this.panel(`Broken down by ${e.label.toLowerCase()}`,"",[a("table",{class:"atfa-table",children:[a("thead",{children:[o]}),a("tbody",{children:i})]})])}questions(e){return a("div",{class:"atfa-questions",children:e.fields.map(t=>this.question(t,e.sampled))})}question(e,t){const n=[];if(e.choices.length){const o=e.choices.reduce((i,r)=>Math.max(i,r.count),0);n.push(a("ul",{class:"atfa-bars",children:e.choices.map(i=>a("li",{class:"atfa-bars__row",children:[a("span",{class:"atfa-bars__label",text:i.label}),a("div",{class:"atfa-meter",children:[a("div",{class:"atfa-meter__fill",attrs:{style:`width:${o?i.count/o*100:0}%`}})]}),a("span",{class:"atfa-bars__value",text:`${i.count} ${e.answered?Math.round(i.count/e.answered*100):0}%`})]}))}))}return e.numbers&&n.push(this.histogram(e)),n.length||n.push(a("p",{class:"atfa-question__note",text:`${e.answered} of ${t} people answered this.`})),this.panel(e.label,this.questionSummary(e,t),n)}questionSummary(e,t){const n=[`${e.rate}% answered`];return e.numbers&&n.push(`mean ${e.numbers.mean}`,`median ${e.numbers.median}`),n.push(`${e.answered}/${t}`),n.join(" · ")}histogram(e){const t=e.numbers;if(!t)return a("div");const n=L(t),o=n.reduce((i,r)=>Math.max(i,r.count),0);return a("div",{class:"atfa-hist",children:n.map(i=>a("div",{class:`atfa-hist__col${i.holdsMean?" is-mean":""}`,attrs:{title:`${i.label}: ${i.count}`},children:[a("span",{class:"atfa-hist__count",text:i.count?String(i.count):""}),a("div",{class:"atfa-hist__bar",attrs:{style:`height:${o?Math.max(2,i.count/o*100):0}%`}}),a("span",{class:"atfa-hist__tick",text:i.label})]}))})}demoPanel(){const e=this.demo,t=a("p",{class:"atfa-demo__status",text:this.demoSummary(e)}),n=I("Fill with demo data",async()=>{await this.seedAll(t)}),o=I("Remove demo data",async()=>{if(await G("Delete the demo survey and every entry it generated?","Remove demo data")){t.textContent="Removing…";try{const i=await f.demoRemove();x("Demo data removed",`${i.entries} entries and ${i.forms} form.`)}catch{x("Could not remove the demo data","","error")}await this.refreshDemo(t)}});return a("div",{class:"atfa-demo",attrs:{"data-atfa-demo":""},children:[a("h3",{class:"atfa-demo__title",text:"Developer"}),a("p",{class:"atfa-demo__note",text:"Generates a team pulse survey and several hundred submissions through the real submission pipeline, so this report has something to report on. Everything it makes is tagged, and removing it deletes exactly that and nothing else."}),t,a("div",{class:"atfa-demo__actions",children:[n,o]})]})}demoSummary(e){return!e||!e.formId?"No demo data on this site.":`${e.title}: ${e.entries} of ${e.target} submissions.`}async seedAll(e){let t=0,n=0;for(;;){try{const o=await f.demoSeed();if(this.demo=o,n=0,e.textContent=`Generating… ${o.entries} of ${o.target}`,o.remaining<1){x("Demo data ready",`${o.entries} submissions.`);break}}catch{if(n+=1,n>=3){e.textContent="Stopped early. Press it again to carry on from here.",x("Could not finish the demo data","Some submissions were made. Try again to continue.","error");break}}if(t+=1,t>200)break}if(await this.refreshDemo(e),this.demo?.formId){try{this.forms=await f.listForms()}catch{}this.formId=this.demo.formId,this.dimension=""}await this.load()}async refreshDemo(e){try{this.demo=await f.demoStatus()}catch{this.demo=null}e.textContent=this.demoSummary(this.demo)}panel(e,t,n){return a("section",{class:"atfa-panel",children:[a("header",{class:"atfa-panel__head",children:[a("h3",{class:"atfa-panel__title",text:e}),t?a("p",{class:"atfa-panel__note",text:t}):null]}),...n]})}revealDemo(){this.body.querySelector("[data-atfa-demo]")?.scrollIntoView({behavior:"smooth",block:"start"})}}let b=null;document.addEventListener("atf-open-analytics-form",s=>{const e=Number(s.detail?.formId??0);e&&b?.showForm(e)});function S(){const s=document.querySelector("[data-atfa-root]");!s||s.dataset.atfaMounted||(s.dataset.atfaMounted="1",W(s),b=new H(s),b.start())}let A=!1;return document.addEventListener("atf-open-demo-panel",()=>{A=!0,window.setTimeout(()=>{b?.revealDemo(),A=!1},600)}),document.addEventListener("os-window-content-loaded",()=>{S(),A&&window.setTimeout(()=>b?.revealDemo(),400)}),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",S):S(),p.MAX_HISTOGRAM_BUCKETS=q,p.histogramBuckets=L,p.mountAnalytics=S,Object.defineProperty(p,Symbol.toStringTag,{value:"Module"}),p}({}); +var allTerrainFormsAnalytics=function(p){"use strict";function F(){return window.wp?.os??null}const c=window.allTerrainForms;class y extends Error{constructor(e,t,n=""){super(e),this.name="ApiError",this.status=t,this.code=n}}function T(s,e){return s.includes("?")?`${s}${e.replace("?","&")}`:`${s}${e}`}async function M(s,e={}){if(!c?.restUrl)throw new y("AllTerrain Forms is not configured on this page.",0);const t=T(c.restUrl,s),n={"Content-Type":"application/json",...e.headers??{}};c.nonce&&(n["X-WP-Nonce"]=c.nonce);const o=F(),r=await(o?.fetch?(l,m)=>o.fetch(l,m,{source:"allterrain-forms"}):(l,m)=>fetch(l,m))(t,{credentials:"same-origin",...e,headers:n});if(!r.ok){let l=`Request failed with status ${r.status}.`,m="";try{const h=await r.json();l=h.message??l,m=h.code??""}catch{}throw new y(l,r.status,m)}if(r.status!==204)return await r.json()}const d=s=>M(s);async function D(s){if(!c?.wpRestUrl)throw new y("AllTerrain Forms is not configured on this page.",0);const e=c.nonce?{"X-WP-Nonce":c.nonce}:{},t=F(),n=T(c.wpRestUrl,s),o=t?.fetch?await t.fetch(n,{credentials:"same-origin",headers:e},{source:"allterrain-forms"}):await fetch(n,{credentials:"same-origin",headers:e});if(!o.ok)throw new y(`Request failed with status ${o.status}.`,o.status);return await o.json()}function j(s){if(!s||!s.includes("&"))return s;const e=document.createElement("textarea");return e.innerHTML=s,e.value}const u=(s,e)=>M(s,{method:"POST",body:JSON.stringify(e)}),w=s=>M(s,{method:"DELETE"});function C(s){const e=new URLSearchParams;for(const[n,o]of Object.entries(s))o===void 0||o===""||o===!1||e.set(n,String(o));const t=e.toString();return t?`?${t}`:""}function g(s){const e=s?.schema?.settings;return e&&(!e.themeOverrides||Array.isArray(e.themeOverrides))&&(e.themeOverrides={...e.themeOverrides}),s}const f={config:()=>d("/config"),listForms:()=>d("/forms"),getForm:s=>d(`/forms/${s}`).then(g),createForm:s=>u("/forms",s).then(g),updateForm:(s,e)=>u(`/forms/${s}`,e).then(g),duplicateForm:s=>u(`/forms/${s}/duplicate`,{}).then(g),deleteForm:s=>w(`/forms/${s}`),preview:(s,e)=>u(`/forms/${s}/preview`,e),pages:()=>D("wp/v2/pages?per_page=100&status=publish&orderby=title&order=asc&_fields=id,title").then(s=>s.map(e=>({id:e.id,title:j(e.title?.rendered??"")||`#${e.id}`}))),mergeTags:s=>d(`/forms/${s}/merge-tags`).then(e=>e.groups),analytics:(s,e="")=>d(`/forms/${s}/analytics${e?`?dimension=${encodeURIComponent(e)}`:""}`),demoStatus:()=>d("/demo"),demoSeed:s=>u("/demo",s?{count:s}:{}),demoRemove:()=>w("/demo"),listEntries:s=>d(`/entries${C(s)}`),getEntry:s=>d(`/entries/${s}`),updateEntry:(s,e)=>u(`/entries/${s}`,e),deleteEntry:s=>w(`/entries/${s}`),exportEntries:s=>d(`/entries/export${C(s)}`),listThemes:()=>d("/themes"),saveTheme:s=>u("/themes",s),deleteTheme:s=>w(`/themes/${s}`)};function P(s){return`allterrain-forms/requested-form-${s}`}function R(s){try{const e=window.sessionStorage.getItem(P(s));return window.sessionStorage.removeItem(P(s)),Number(e)||0}catch{return 0}}function a(s,e={}){const t=document.createElement(s);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[n,o]of Object.entries(e.attrs??{}))o===void 0||o===!1||t.setAttribute(n,o===!0?"":String(o));Object.assign(t.style,e.style??{});for(const[n,o]of Object.entries(e.on??{}))t.addEventListener(n,o);for(const n of e.children??[])n==null||n===!1||t.append(n);return t}function _(s){s.replaceChildren()}function N(s,e,t){if(E("os-select")&&E("os-option")){const n=document.createElement("os-select");n.setAttribute("value",s),n.classList.add("atfb-field");for(const o of e){const i=document.createElement("os-option");i.setAttribute("value",o.value),i.textContent=o.label,n.append(i)}return n.addEventListener("os-pick",o=>{t(String(o.detail?.value??""))}),n}return a("select",{class:"atfb-input atfb-select",on:{change:n=>t(n.target.value)},children:e.map(n=>a("option",{value:n.value,text:n.label,attrs:{selected:n.value===s}}))})}function I(s,e,t="secondary",n){const o=[null,a("span",{text:s})];if(E("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 r of o)r&&i.append(r);return i}return a("button",{class:`atfb-button atfb-button--${t}`,type:"button",on:{click:e},children:o})}function E(s){return typeof customElements<"u"&&!!customElements.get(s)}const B=["os-button","os-checkbox-label","os-number-field","os-select","os-option","os-segmented","os-segment","os-color-field","os-empty-state"];let $=null;function U(){if($)return $;const s=window.wp?.os;return $=s?.loadComponents?s.loadComponents(B).catch(()=>{}):Promise.resolve(),$}async function G(s,e=""){const t=window.wp?.os;return t?.confirm?t.confirm({title:e,message:s,danger:!0}):window.confirm(s)}function x(s,e="",t="info"){const n=window.wp?.os;if(n?.notify){n.notify({title:s,body:e,type:t});return}console.info(`[AllTerrain Forms] ${s}${e?`: ${e}`:""}`)}function W(s){const e=s.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)}const O=[{key:"detractors",label:"Detractors",hint:"0–6"},{key:"passives",label:"Passives",hint:"7–8"},{key:"promoters",label:"Promoters",hint:"9–10"}],q=50;function L(s){const e=Math.floor(s.min),t=Math.ceil(s.max);if(!Number.isFinite(e)||!Number.isFinite(t)||tMath.min(i-1,Math.max(0,Math.floor((h-e)/o))),l=[];for(let h=0;ht.id===e)?this.formId=e:this.formId||(this.formId=this.forms[0].id),await this.load()}async showForm(e){this.formId=e,this.dimension="",await this.load()}async load(){try{this.report=await f.analytics(this.formId,this.dimension)}catch{this.fail("Could not load that report.");return}this.dimension=this.report.breakdown?.id??"",this.renderBar(),this.render()}fail(e){_(this.bar),_(this.body),this.body.append(a("p",{class:"atfa__empty",text:e}))}renderBar(){_(this.bar),this.bar.append(N(String(this.formId),this.forms.map(t=>({value:String(t.id),label:t.title||`#${t.id}`})),t=>{this.formId=Number(t),this.dimension="",this.load()}));const e=this.report?.dimensions??[];e.length>1&&this.bar.append(a("span",{class:"atfa__bar-label",text:"Group by"}),N(this.dimension,e.map(t=>({value:t.id,label:t.label})),t=>{this.dimension=t,this.load()}))}render(){const e=this.report;if(_(this.body),!!e){if(this.body.append(this.kpis(e)),e.submissions<1&&e.sampled<1)this.body.append(a("p",{class:"atfa__empty",text:"No submissions yet. Everything below fills in as they arrive."}));else{this.body.append(this.timeline(e));for(const t of e.fields)t.nps&&this.body.append(this.npsPanel(t));e.breakdown&&e.breakdown.groups.length&&this.body.append(this.breakdownPanel(e.breakdown)),this.body.append(this.questions(e))}this.demoAvailable&&this.body.append(this.demoPanel())}}kpis(e){const t=[{label:"Submissions",value:String(e.submissions)},{label:"Views",value:String(e.views)},{label:"Conversion",value:`${e.conversion}%`,hint:"of people who saw it"},{label:"Completion",value:`${e.completion}%`,hint:"of people who started"},{label:"Unread",value:String(e.unread)},{label:"Spam",value:String(e.spam)}];return a("div",{class:"atfa-kpis",children:t.map(n=>a("div",{class:"atfa-kpi",children:[a("span",{class:"atfa-kpi__value",text:n.value}),a("span",{class:"atfa-kpi__label",text:n.label}),n.hint?a("span",{class:"atfa-kpi__hint",text:n.hint}):null]}))})}timeline(e){const t=e.timeline,n=t.reduce((i,r)=>Math.max(i,r.count),0),o=t.reduce((i,r)=>i+r.count,0);return this.panel("When they answered",`${o} in the last ${t.length} days · busiest day ${n}`,[a("div",{class:"atfa-timeline",children:t.map(i=>a("div",{class:`atfa-timeline__day${i.count?"":" is-empty"}`,attrs:{style:`height:${n?Math.max(2,i.count/n*100):0}%`,title:`${i.date}: ${i.count}`}}))}),a("div",{class:"atfa-timeline__axis",children:[a("span",{text:t[0]?.date??""}),a("span",{text:t[t.length-1]?.date??""})]})])}npsPanel(e){const t=e.nps;if(!t)return a("div");const n=o=>t.responses?o/t.responses*100:0;return this.panel(e.label,`${t.responses} answers · Net Promoter Score`,[a("div",{class:"atfa-nps",children:[a("div",{class:`atfa-nps__score is-${t.score>=50?"great":t.score>=0?"ok":"poor"}`,children:[a("span",{class:"atfa-nps__number",text:`${t.score>0?"+":""}${t.score}`}),a("span",{class:"atfa-nps__caption",text:"NPS"})]}),a("div",{class:"atfa-nps__detail",children:[a("div",{class:"atfa-nps__bar",children:O.map(o=>a("div",{class:`atfa-nps__band is-${o.key}`,attrs:{style:`width:${n(t[o.key])}%`,title:`${o.label} (${o.hint}): ${t[o.key]}`}}))}),a("div",{class:"atfa-nps__legend",children:O.map(o=>a("span",{class:`atfa-nps__key is-${o.key}`,children:[a("strong",{text:String(t[o.key])}),a("span",{text:` ${o.label} `}),a("small",{text:o.hint})]}))})]})]})])}breakdownPanel(e){const t=e.groups[0]?.metrics??[];if(!t.length)return a("div");const n=new Map;for(const r of e.groups)for(const l of r.metrics)n.set(l.id,Math.max(n.get(l.id)??0,l.mean));const o=a("tr",{children:[a("th",{text:e.label,attrs:{scope:"col"}}),a("th",{text:"Answers",attrs:{scope:"col"}}),...t.map(r=>a("th",{text:r.label,attrs:{scope:"col"}}))]}),i=e.groups.map(r=>a("tr",{children:[a("th",{class:"atfa-table__label",text:r.label,attrs:{scope:"row"}}),a("td",{class:"atfa-table__count",text:String(r.count)}),...r.metrics.map(l=>a("td",{children:[a("div",{class:"atfa-meter",children:[a("div",{class:"atfa-meter__fill",attrs:{style:`width:${l.mean/(n.get(l.id)||1)*100}%`}})]}),a("span",{class:"atfa-meter__value",text:l.nps!==null?`${l.mean.toFixed(1)} · NPS ${l.nps>0?"+":""}${l.nps}`:l.mean.toFixed(2)})]}))]}));return this.panel(`Broken down by ${e.label.toLowerCase()}`,"",[a("table",{class:"atfa-table",children:[a("thead",{children:[o]}),a("tbody",{children:i})]})])}questions(e){return a("div",{class:"atfa-questions",children:e.fields.map(t=>this.question(t,e.sampled))})}question(e,t){const n=[];if(e.choices.length){const o=e.choices.reduce((i,r)=>Math.max(i,r.count),0);n.push(a("ul",{class:"atfa-bars",children:e.choices.map(i=>a("li",{class:"atfa-bars__row",children:[a("span",{class:"atfa-bars__label",text:i.label}),a("div",{class:"atfa-meter",children:[a("div",{class:"atfa-meter__fill",attrs:{style:`width:${o?i.count/o*100:0}%`}})]}),a("span",{class:"atfa-bars__value",text:`${i.count} ${e.answered?Math.round(i.count/e.answered*100):0}%`})]}))}))}return e.numbers&&n.push(this.histogram(e)),n.length||n.push(a("p",{class:"atfa-question__note",text:`${e.answered} of ${e.of??t} ${e.unit??"people"} answered this.`})),this.panel(e.label,this.questionSummary(e,t),n)}questionSummary(e,t){const n=[`${e.rate}% answered`];return e.numbers&&n.push(`mean ${e.numbers.mean}`,`median ${e.numbers.median}`),n.push(`${e.answered}/${e.of??t}`),n.join(" · ")}histogram(e){const t=e.numbers;if(!t)return a("div");const n=L(t),o=n.reduce((i,r)=>Math.max(i,r.count),0);return a("div",{class:"atfa-hist",children:n.map(i=>a("div",{class:`atfa-hist__col${i.holdsMean?" is-mean":""}`,attrs:{title:`${i.label}: ${i.count}`},children:[a("span",{class:"atfa-hist__count",text:i.count?String(i.count):""}),a("div",{class:"atfa-hist__bar",attrs:{style:`height:${o?Math.max(2,i.count/o*100):0}%`}}),a("span",{class:"atfa-hist__tick",text:i.label})]}))})}demoPanel(){const e=this.demo,t=a("p",{class:"atfa-demo__status",text:this.demoSummary(e)}),n=I("Fill with demo data",async()=>{await this.seedAll(t)}),o=I("Remove demo data",async()=>{if(await G("Delete the demo survey and every entry it generated?","Remove demo data")){t.textContent="Removing…";try{const i=await f.demoRemove();x("Demo data removed",`${i.entries} entries and ${i.forms} form.`)}catch{x("Could not remove the demo data","","error")}await this.refreshDemo(t)}});return a("div",{class:"atfa-demo",attrs:{"data-atfa-demo":""},children:[a("h3",{class:"atfa-demo__title",text:"Developer"}),a("p",{class:"atfa-demo__note",text:"Generates a team pulse survey and several hundred submissions through the real submission pipeline, so this report has something to report on. Everything it makes is tagged, and removing it deletes exactly that and nothing else."}),t,a("div",{class:"atfa-demo__actions",children:[n,o]})]})}demoSummary(e){return!e||!e.formId?"No demo data on this site.":`${e.title}: ${e.entries} of ${e.target} submissions.`}async seedAll(e){let t=0,n=0;for(;;){try{const o=await f.demoSeed();if(this.demo=o,n=0,e.textContent=`Generating… ${o.entries} of ${o.target}`,o.remaining<1){x("Demo data ready",`${o.entries} submissions.`);break}}catch{if(n+=1,n>=3){e.textContent="Stopped early. Press it again to carry on from here.",x("Could not finish the demo data","Some submissions were made. Try again to continue.","error");break}}if(t+=1,t>200)break}if(await this.refreshDemo(e),this.demo?.formId){try{this.forms=await f.listForms()}catch{}this.formId=this.demo.formId,this.dimension=""}await this.load()}async refreshDemo(e){try{this.demo=await f.demoStatus()}catch{this.demo=null}e.textContent=this.demoSummary(this.demo)}panel(e,t,n){return a("section",{class:"atfa-panel",children:[a("header",{class:"atfa-panel__head",children:[a("h3",{class:"atfa-panel__title",text:e}),t?a("p",{class:"atfa-panel__note",text:t}):null]}),...n]})}revealDemo(){this.body.querySelector("[data-atfa-demo]")?.scrollIntoView({behavior:"smooth",block:"start"})}}let b=null;document.addEventListener("atf-open-analytics-form",s=>{const e=Number(s.detail?.formId??0);e&&b?.showForm(e)});function S(){const s=document.querySelector("[data-atfa-root]");!s||s.dataset.atfaMounted||(s.dataset.atfaMounted="1",W(s),b=new H(s),b.start())}let A=!1;return document.addEventListener("atf-open-demo-panel",()=>{A=!0,window.setTimeout(()=>{b?.revealDemo(),A=!1},600)}),document.addEventListener("os-window-content-loaded",()=>{S(),A&&window.setTimeout(()=>b?.revealDemo(),400)}),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",S):S(),p.MAX_HISTOGRAM_BUCKETS=q,p.histogramBuckets=L,p.mountAnalytics=S,Object.defineProperty(p,Symbol.toStringTag,{value:"Module"}),p}({}); diff --git a/assets/js/builder.js b/assets/js/builder.js index c1c5bcd..2e57bf9 100644 --- a/assets/js/builder.js +++ b/assets/js/builder.js @@ -1043,7 +1043,7 @@ var allTerrainFormsBuilder = function(exports) { scale: "summary", likert: "summary", signature: "summary", - repeater: "summary", + repeater: "repeater", color: "text", name: "composite", address: "composite", @@ -1208,6 +1208,8 @@ var allTerrainFormsBuilder = function(exports) { }); case "static": return staticBlock(field, type, handlers); + case "repeater": + return repeaterContainer(field, handlers); default: return el("div", { class: "atfb-preview__stack", @@ -1215,16 +1217,116 @@ var allTerrainFormsBuilder = function(exports) { el("p", { class: "atfb-preview__summary", text: `${type?.label ?? field.type} — set this up in the panel on the right.` - }), - // A repeater's one visible piece of chrome is the button that adds - // another row, and its wording is already a setting. Drawing the - // real button is both the honest preview and the only place the - // wording was reachable. - "repeater" === field.type ? repeatButton(field, handlers) : null + }) ] }); } } + function repeaterContainer(field, handlers) { + const subs = field.fields ?? []; + const zone = el("div", { + class: "atfb-repeater__zone", + attrs: { "data-atfb-repeater-zone": field.id } + }); + subs.forEach((sub) => zone.append(repeaterSubCard(field, sub, handlers))); + if (!subs.length) { + zone.append( + el("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." + }) + ); + } + return el("div", { + class: "atfb-repeater", + attrs: { "data-atfb-repeater": field.id }, + children: [ + el("div", { + class: "atf-repeater__row atfb-repeater__frame", + children: [ + el("div", { + class: "atf-repeater__row-head", + children: [ + el("span", { + class: "atf-repeater__title atfb-repeater__title", + children: [ + editableText({ + value: String(field.itemLabel ?? ""), + placeholder: "Row", + class: "atfb-repeater__item-label", + bind: "itemLabel", + onInput: (value) => handlers.edit((live) => { + live.itemLabel = value; + }) + }), + el("span", { class: "atfb-repeater__ordinal", text: "1", attrs: { "aria-hidden": "true" } }) + ] + }) + ] + }), + zone + ] + }), + repeatButton(field, handlers) + ] + }); + } + function repeaterSubCard(repeater, sub, handlers) { + const subId = sub.id; + const forSub = (apply) => (live) => { + const target = (live.fields ?? []).find((candidate) => candidate.id === subId); + if (target) { + apply(target); + } + }; + const subHandlers = { + edit: (apply) => handlers.edit(forSub(apply)), + restructure: (apply) => handlers.restructure(forSub(apply)), + types: handlers.types, + selectedId: handlers.selectedId + }; + const type = handlers.types?.(sub.type); + return el("div", { + class: `atfb-subcard${handlers.selectedId === subId ? " is-selected" : ""}`, + attrs: { + "data-atfb-subfield": subId, + "data-atfb-parent": repeater.id, + tabindex: "0", + role: "button", + "aria-label": `${sub.label || type?.label || sub.type}, inside ${repeater.label || "the repeater"}` + }, + children: [ + el("div", { + class: "atfb-subcard__head", + children: [ + el("span", { class: "atfb-subcard__type", text: type?.label ?? sub.type }), + sub.required ? el("span", { class: "atfb-subcard__required", text: "Required" }) : null, + el("button", { + class: "atfb-preview__remove", + type: "button", + title: "Remove this field from the repeater", + attrs: { "aria-label": `Remove ${sub.label || type?.label || "this field"}` }, + on: { + pointerdown: (event) => event.stopPropagation(), + click: (event) => { + event.stopPropagation(); + handlers.restructure((live) => { + const list = live.fields ?? []; + const index = list.findIndex((candidate) => candidate.id === subId); + if (index >= 0) { + list.splice(index, 1); + } + }); + } + }, + children: [el("span", { text: "×" })] + }) + ] + }), + renderFieldPreview(sub, type, subHandlers) + ] + }); + } function placeholderBox(field, className, handlers, tall = false) { const box = editableText({ value: field.placeholder ?? "", @@ -1256,7 +1358,7 @@ var allTerrainFormsBuilder = function(exports) { return labelledButton( String(field.addLabel ?? ""), "Add another", - "atf-button--secondary", + "atf-button--ghost atfb-repeater__add", "addLabel", handlers, (live, value) => { @@ -2452,12 +2554,75 @@ var allTerrainFormsBuilder = function(exports) { return result === null || !Number.isFinite(result) ? null : result; } function resolveRefs(formula, values, fields) { - return formula.replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, fieldId) => { - const value = Object.prototype.hasOwnProperty.call(values, fieldId) ? values[fieldId] : null; - const field = fields.find((candidate) => candidate.id === fieldId) ?? null; - const literal = numericValue(value, field).toFixed(10).replace(/0+$/, "").replace(/\.$/, ""); - return literal === "" || literal === "-" ? "0" : literal; - }); + return formula.replace( + /\{([a-zA-Z0-9_]+)(?:\.([a-zA-Z0-9_]+))?\}/g, + (match, fieldId, subId, offset) => refLiteral(formula, offset, match.length, fieldId, subId ?? "", values, fields) + ); + } + function refLiteral(formula, offset, length, fieldId, subId, values, fields) { + const field = findField(fields, fieldId); + const value = Object.prototype.hasOwnProperty.call(values, fieldId) ? values[fieldId] : null; + if (subId === "") { + const number = field?.type === "repeater" ? repeaterRows(value).length : numericValue(value, field); + return numberLiteral(number); + } + const subs = field?.fields ?? []; + const sub = subs.find((candidate) => candidate.id === subId) ?? null; + const numbers = repeaterRows(value).map( + (row2) => numericValue(row2[subId] ?? "", sub) + ); + if (!numbers.length) { + return "0"; + } + const literals = numbers.map(numberLiteral); + if (refSpreads(formula, offset, length)) { + return literals.join(", "); + } + return literals.length === 1 ? literals[0] : `( ${literals.join(" + ")} )`; + } + function findField(fields, fieldId) { + for (const field of fields) { + if (field.id === fieldId) { + return field; + } + for (const sub of field.fields ?? []) { + if (sub.id === fieldId) { + return sub; + } + } + } + return null; + } + function refSpreads(formula, offset, length) { + const after = formula.slice(offset + length).replace(/^\s+/, ""); + if (after === "" || after[0] !== ")") { + return false; + } + const before = formula.slice(0, offset).replace(/\s+$/, ""); + const match = /([a-zA-Z_][a-zA-Z0-9_]*)\s*\($/.exec(before); + return !!match && ["sum", "avg", "min", "max"].includes(match[1].toLowerCase()); + } + function repeaterRows(value) { + if (!Array.isArray(value)) { + return []; + } + const rows = []; + for (const row2 of value) { + if (!row2 || typeof row2 !== "object" || Array.isArray(row2)) { + continue; + } + const filled = Object.values(row2).some( + (item) => item !== "" && item !== null && item !== void 0 && item !== false && !(Array.isArray(item) && !item.length) + ); + if (filled) { + rows.push(row2); + } + } + return rows; + } + function numberLiteral(number) { + const literal = number.toFixed(10).replace(/0+$/, "").replace(/\.$/, ""); + return literal === "" || literal === "-" ? "0" : literal; } function numericValue(value, field) { if (typeof value === "boolean") { @@ -2734,11 +2899,39 @@ var allTerrainFormsBuilder = function(exports) { function formulaTargets(fields, except) { return fields.filter((field) => field.id !== except && NUMERIC_FRIENDLY.includes(field.type)); } + function repeaterReferences(fields) { + const references = []; + for (const field of fields) { + if (field.type !== "repeater") { + continue; + } + const name = field.label || field.id; + references.push({ label: `${name} (how many)`, insert: `{${field.id}}` }); + for (const sub of field.fields ?? []) { + if (!NUMERIC_FRIENDLY.includes(sub.type)) { + continue; + } + references.push({ + label: `${name} · ${sub.label || sub.id}`, + insert: `{${field.id}.${sub.id}}` + }); + } + } + return references; + } function formulaSampleValues(fields, except) { const values = {}; formulaTargets(fields, except).forEach((field, index) => { values[field.id] = index + 1; }); + for (const field of fields) { + if (field.type !== "repeater" || field.id === except) { + continue; + } + const subs = field.fields ?? []; + const row2 = (bump) => Object.fromEntries(subs.map((sub, index) => [sub.id, index + 1 + bump])); + values[field.id] = [row2(0), row2(1)]; + } return values; } function openFormulaEditor(options) { @@ -2772,7 +2965,9 @@ var allTerrainFormsBuilder = function(exports) { result.classList.add("is-error"); return; } - const sampled = formulaTargets(options.fields, options.field.id).map((field, index) => `${field.label || field.id} = ${index + 1}`).join(", "); + const sampled = formulaTargets(options.fields, options.field.id).map((field, index) => `${field.label || field.id} = ${index + 1}`).concat( + options.fields.filter((field) => field.type === "repeater" && field.id !== options.field.id).map((field) => `${field.label || field.id} = 2 sample rows`) + ).join(", "); result.textContent = `With sample answers (${sampled}): ${computed}`; result.classList.remove("is-error"); }; @@ -2792,9 +2987,13 @@ var allTerrainFormsBuilder = function(exports) { } }); const targets = formulaTargets(options.fields, options.field.id); + const repeaters = repeaterReferences(options.fields); const questions = el("div", { class: "atfb-formula__chips", - children: targets.length ? targets.map((field) => chip(field.label || field.id, `{${field.id}}`)) : [el("p", { class: "atfb-hint", text: "No number-shaped questions yet — add a number, scale or priced choice field and it appears here." })] + children: targets.length || repeaters.length ? [ + ...targets.map((field) => chip(field.label || field.id, `{${field.id}}`)), + ...repeaters.map((reference) => chip(reference.label, reference.insert)) + ] : [el("p", { class: "atfb-hint", text: "No number-shaped questions yet — add a number, scale or priced choice field and it appears here." })] }); const functions = el("div", { class: "atfb-formula__chips", @@ -3697,6 +3896,12 @@ var allTerrainFormsBuilder = function(exports) { 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", @@ -3848,7 +4053,9 @@ var allTerrainFormsBuilder = function(exports) { if (this.tab !== "build") { return; } - const field = this.schema.fields.find((candidate) => candidate.id === this.selected); + const located = this.selected ? this.locateField(this.selected) : void 0; + const field = located?.field; + const parent = located?.parent ?? null; this.root.classList.toggle("atfb--has-selection", !!field); if (!field) { this.inspector.append( @@ -3876,10 +4083,23 @@ var allTerrainFormsBuilder = function(exports) { this.markDirty(); this.renderCanvas(); }; - this.inspector.append( - el("h3", { class: "atfb-inspector__title", text: definition?.label ?? field.type }), - el("p", { class: "atfb-hint", text: `Reference this field as {field:${field.id}}` }) - ); + this.inspector.append(el("h3", { class: "atfb-inspector__title", text: definition?.label ?? field.type })); + if (parent) { + this.inspector.append( + el("p", { + class: "atfb-hint atfb-inspector__crumb", + text: `Inside ${parent.label || "a repeater"} — the visitor answers this once per ${String(parent.itemLabel ?? "") || "row"}.` + }), + el("p", { + class: "atfb-hint", + text: `Formulas aggregate it as {${parent.id}.${field.id}} — e.g. sum( {${parent.id}.${field.id}} ).` + }) + ); + } else { + this.inspector.append( + el("p", { class: "atfb-hint", text: `Reference this field as {field:${field.id}}` }) + ); + } if (supports.includes("label")) { this.inspector.append( row( @@ -4001,9 +4221,11 @@ var allTerrainFormsBuilder = function(exports) { ); } this.inspector.append(this.renderValidationSection(field, supports, update)); - this.inspector.append(this.renderLogicSection(field)); - if (supports.includes("prefill")) { - this.inspector.append(this.prefillControl(field, update)); + if (!parent) { + this.inspector.append(this.renderLogicSection(field)); + if (supports.includes("prefill")) { + this.inspector.append(this.prefillControl(field, update)); + } } if (supports.includes("css")) { this.inspector.append( @@ -4195,7 +4417,30 @@ var allTerrainFormsBuilder = function(exports) { * @return The live field, if it is still there. */ liveField(fieldId) { - return this.schema?.fields.find((candidate) => candidate.id === fieldId); + return this.locateField(fieldId)?.field; + } + /** + * A field found wherever it lives — the top level, or inside a repeater — + * along with the list holding it, so a caller can move or remove it. + * + * @param fieldId The field's id. + * @return The field, the list it sits in, its index there, and the repeater + * containing it (null at the top level). + */ + locateField(fieldId) { + const fields = this.schema?.fields ?? []; + for (let index = 0; index < fields.length; index++) { + if (fields[index].id === fieldId) { + return { field: fields[index], list: fields, index, parent: null }; + } + const subs = fields[index].fields ?? []; + for (let at = 0; at < subs.length; at++) { + if (subs[at].id === fieldId) { + return { field: subs[at], list: subs, index: at, parent: fields[index] }; + } + } + } + return void 0; } /** * Writes a field's current values into its card on the canvas. @@ -4219,9 +4464,7 @@ var allTerrainFormsBuilder = function(exports) { * @param field The field that was edited in the inspector. */ syncCanvas(field) { - const card = this.canvas.querySelector( - `[data-atfb-card="${CSS.escape(field.id)}"]` - ); + const card = this.canvas.querySelector(`[data-atfb-card="${CSS.escape(field.id)}"]`) ?? this.canvas.querySelector(`[data-atfb-subfield="${CSS.escape(field.id)}"]`); if (!card) { return; } @@ -5322,6 +5565,10 @@ var allTerrainFormsBuilder = function(exports) { apply(live); this.markDirty(); this.syncInspector(live); + const selected2 = this.selected ? this.locateField(this.selected) : void 0; + if (selected2?.parent && selected2.parent.id === live.id) { + this.syncInspector(selected2.field); + } }, restructure: (apply) => { const live = this.liveField(field.id); @@ -5333,7 +5580,12 @@ var allTerrainFormsBuilder = function(exports) { this.markDirty(); this.renderCanvas(); this.renderInspector(); - } + }, + // A repeater draws each sub-field through the same + // preview machinery, and needs to know their types + // and which of them is selected. + types: (name) => this.config?.fieldTypes.find((candidate) => candidate.type === name), + selectedId: this.selected }), condition.length ? this.renderCondition(field, condition) : null ] @@ -5461,8 +5713,8 @@ var allTerrainFormsBuilder = function(exports) { this.addField(data.fieldType, index); return; } - if (data.fieldId && this.schema?.fields.some((field) => field.id === data.fieldId)) { - this.moveField(data.fieldId, index); + if (data.fieldId && this.locateField(data.fieldId)) { + this.relocateField(data.fieldId, null, index); return; } if (data.field) { @@ -5470,6 +5722,7 @@ var allTerrainFormsBuilder = function(exports) { } } }); + const zoneTeardowns = this.wireRepeaterZones(list); const onMove = (event) => { const detail = event.detail; if (detail?.payload?.type !== FIELD_PAYLOAD_TYPE || !list.classList.contains("is-dropping")) { @@ -5492,15 +5745,140 @@ var allTerrainFormsBuilder = function(exports) { document.addEventListener("os.drag.move", onMove); this.canvasTarget = () => { teardown(); + zoneTeardowns.forEach((zoneTeardown) => zoneTeardown()); document.removeEventListener("os.drag.move", onMove); }; } + /** + * Makes every repeater on the canvas a drop target of its own, and its + * sub-field cards draggable, selectable and keyboard-operable. + * + * The zone elements are drawn by the preview (`field-preview.ts`), which + * cannot reach the drag manager; this is where they come alive. Zones are + * nested inside the canvas target, and the manager resolves hits depth + * first, so a drop lands in the repeater when it is over one and on the + * canvas when it is not. + * + * @param list The canvas list, freshly rendered. + * @return One teardown per registered zone. + */ + wireRepeaterZones(list) { + const teardowns = []; + list.querySelectorAll("[data-atfb-repeater-zone]").forEach((zone) => { + const repeaterId = zone.dataset.atfbRepeaterZone ?? ""; + teardowns.push( + getDragManager().registerDropTarget({ + id: `atfb-repzone-${this.form?.id ?? 0}-${repeaterId}`, + element: zone, + accept: (payload) => { + if (payload.type !== FIELD_PAYLOAD_TYPE) { + return false; + } + const data = payload.data; + if (data.fieldId === repeaterId) { + return false; + } + const type = data.isNew ? data.fieldType : this.locateField(data.fieldId ?? "")?.field.type ?? data.field?.type; + return !!type && this.allowedInRepeater(type); + }, + onEnter: () => zone.classList.add("is-dropping"), + onLeave: () => zone.classList.remove("is-dropping"), + onDrop: (session, position) => { + zone.classList.remove("is-dropping"); + const data = session.payload.data; + const source = data.fieldId ? list.querySelector( + `[data-atfb-subfield="${CSS.escape(data.fieldId)}"]` + ) : null; + const index = insertionIndex(zone, ".atfb-subcard", position.clientY, source ?? void 0); + if (data.isNew && data.fieldType) { + this.addFieldToRepeater(data.fieldType, repeaterId, index); + return; + } + if (data.fieldId && this.locateField(data.fieldId)) { + this.relocateField(data.fieldId, repeaterId, index); + return; + } + if (data.field) { + this.insertFieldIntoRepeater({ ...data.field, id: "" }, repeaterId, index); + } + } + }) + ); + zone.querySelectorAll(".atfb-subcard").forEach((card) => { + const subId = card.dataset.atfbSubfield ?? ""; + card.addEventListener("click", (event) => { + if (event.target.closest(".atfb-preview__remove")) { + return; + } + event.stopPropagation(); + if (getDragManager().recentlyEndedDrag()) { + return; + } + this.selectField(subId); + }); + card.addEventListener("keydown", (event) => { + event.stopPropagation(); + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + this.selectField(subId); + return; + } + if (event.altKey && (event.key === "ArrowUp" || event.key === "ArrowDown")) { + event.preventDefault(); + const now = this.locateField(subId); + if (now) { + this.relocateField( + subId, + repeaterId, + event.key === "ArrowUp" ? now.index - 1 : now.index + 1 + ); + window.requestAnimationFrame(() => { + this.canvas.querySelector(`[data-atfb-subfield="${CSS.escape(subId)}"]`)?.focus(); + }); + } + return; + } + if (event.key === "Delete" || event.key === "Backspace") { + event.preventDefault(); + void this.deleteField(subId); + } + }); + card.addEventListener("pointerdown", (event) => { + event.stopPropagation(); + if (event.target.closest(".atfb-preview__remove")) { + return; + } + const found = this.locateField(subId); + if (!found) { + return; + } + getDragManager().start({ + payload: buildPayload( + FIELD_PAYLOAD_TYPE, + card, + { fieldId: subId, parentId: repeaterId, field: found.field, isNew: false }, + event + ), + origin: event + }); + }); + }); + }); + return teardowns; + } /* -------------------------------------------------------- Field editing */ /** Adds a field of a type, at an index or at the end. */ addField(type, index) { + const field = this.buildField(type); + if (field) { + this.insertField(field, index); + } + } + /** A brand-new field of a type, with the type's defaults and a fresh id. */ + buildField(type) { const definition = this.config?.fieldTypes.find((candidate) => candidate.type === type); if (!definition || !this.schema) { - return; + return void 0; } const field = { id: this.nextFieldId(), @@ -5521,7 +5899,94 @@ var allTerrainFormsBuilder = function(exports) { prefill: "", ...definition.settings }; - this.insertField(field, index); + return field; + } + /** + * Whether a field type may live inside a repeater. + * + * The exclusions are each a real constraint, not taste: a repeater inside + * a repeater has no addressable rows; a page break splits a *form*, not a + * row; file uploads and signatures are wired to top-level names in the + * submission pipeline; and a total is computed once per form, so a copy + * per row would be a number that lies. Layout blocks are out because a + * repeater's rows are answers, and analytics reads them as answers. + */ + allowedInRepeater(type) { + const definition = this.config?.fieldTypes.find((candidate) => candidate.type === type); + if (!definition || !definition.input) { + return false; + } + return !["repeater", "page_break", "file", "signature", "total"].includes(type); + } + /** Adds a brand-new field of a type inside a repeater. */ + addFieldToRepeater(type, repeaterId, index) { + if (!this.allowedInRepeater(type)) { + return; + } + const field = this.buildField(type); + if (field) { + this.insertFieldIntoRepeater(field, repeaterId, index); + } + } + /** Puts a field into a repeater's sub-field list. */ + insertFieldIntoRepeater(field, repeaterId, index) { + const repeater = this.liveField(repeaterId); + if (!this.schema || !repeater || repeater.type !== "repeater") { + return; + } + if (!field.id) { + field.id = this.nextFieldId(); + } + this.snapshot(); + if (!Array.isArray(repeater.fields)) { + repeater.fields = []; + } + const list = repeater.fields; + const at = index === void 0 ? list.length : Math.max(0, Math.min(index, list.length)); + list.splice(at, 0, field); + this.selected = field.id; + this.markDirty(); + this.renderCanvas(); + this.renderInspector(); + window.requestAnimationFrame(() => { + this.canvas.querySelector(`[data-atfb-subfield="${CSS.escape(field.id)}"]`)?.focus(); + }); + } + /** + * Moves a field to wherever it was dropped — the top level, or inside a + * repeater — from wherever it was. + * + * `index` counts the destination list *without* the moved field, exactly + * as `insertionIndex()` computes it with the dragged card excluded. + */ + relocateField(fieldId, repeaterId, index) { + if (!this.schema) { + return; + } + const found = this.locateField(fieldId); + if (!found) { + return; + } + if (!found.parent && !repeaterId) { + this.moveField(fieldId, index); + return; + } + const repeater = repeaterId ? this.liveField(repeaterId) : null; + if (repeaterId && (!repeater || repeater.type !== "repeater" || fieldId === repeaterId)) { + return; + } + if (repeater && !Array.isArray(repeater.fields)) { + repeater.fields = []; + } + const targetList = repeater ? repeater.fields : this.schema.fields; + this.snapshot(); + found.list.splice(found.index, 1); + const at = Math.max(0, Math.min(index, targetList.length)); + targetList.splice(at, 0, found.field); + this.selected = found.field.id; + this.markDirty(); + this.renderCanvas(); + this.renderInspector(); } /** Puts a field into the schema. */ insertField(field, index) { @@ -5567,19 +6032,45 @@ var allTerrainFormsBuilder = function(exports) { if (!this.schema) { return; } - const index = this.schema.fields.findIndex((field) => field.id === fieldId); - if (index < 0) { + const found = this.locateField(fieldId); + if (!found) { return; } - const copy = JSON.parse(JSON.stringify(this.schema.fields[index])); - copy.id = this.nextFieldId(); - this.insertField(copy, index + 1); + const copy = JSON.parse(JSON.stringify(found.field)); + const used = this.usedFieldIds(); + const mint = () => { + let index = used.size + 1; + while (used.has(`f${index}`)) { + index++; + } + const id = `f${index}`; + used.add(id); + return id; + }; + copy.id = mint(); + for (const sub of copy.fields ?? []) { + sub.id = mint(); + } + if (found.parent) { + this.snapshot(); + found.list.splice(found.index + 1, 0, copy); + this.selected = copy.id; + this.markDirty(); + this.renderCanvas(); + this.renderInspector(); + return; + } + this.insertField(copy, found.index + 1); } - /** Removes a field. */ + /** Removes a field, wherever it lives. */ async deleteField(fieldId) { if (!this.schema) { return; } + const found = this.locateField(fieldId); + if (!found) { + return; + } const dependents = this.schema.fields.filter( (field) => field.logic?.rules?.some((rule) => rule.field === fieldId) ); @@ -5588,7 +6079,7 @@ var allTerrainFormsBuilder = function(exports) { return; } this.snapshot(); - this.schema.fields = this.schema.fields.filter((field) => field.id !== fieldId); + found.list.splice(found.index, 1); if (this.selected === fieldId) { this.selected = null; } @@ -5604,18 +6095,39 @@ var allTerrainFormsBuilder = function(exports) { card.classList.toggle("is-selected", isSelected); card.setAttribute("aria-pressed", isSelected ? "true" : "false"); } + for (const card of this.canvas.querySelectorAll("[data-atfb-subfield]")) { + card.classList.toggle("is-selected", card.dataset.atfbSubfield === fieldId); + } this.logicMap?.highlight(fieldId); this.renderInspector(); } - /** A field id not already in use. */ + /** + * A field id not already in use — anywhere, repeater sub-fields included. + * + * Sub-fields draw from the same pool as top-level fields because a formula + * names them as `{repeater.sub}` and a field can be dragged out of a + * repeater onto the canvas: an id that collided the moment it surfaced + * would make both of those ambiguous. + */ nextFieldId() { - const used = new Set((this.schema?.fields ?? []).map((field) => field.id)); + const used = this.usedFieldIds(); let index = used.size + 1; while (used.has(`f${index}`)) { index++; } return `f${index}`; } + /** Every field id in the schema, repeater sub-fields included. */ + usedFieldIds() { + const used = /* @__PURE__ */ new Set(); + for (const field of this.schema?.fields ?? []) { + used.add(field.id); + for (const sub of field.fields ?? []) { + used.add(sub.id); + } + } + return used; + } /** * An id for a new notification or confirmation, not already in use. * diff --git a/assets/js/builder.min.js b/assets/js/builder.min.js index 142750b..fc79277 100644 --- a/assets/js/builder.min.js +++ b/assets/js/builder.min.js @@ -1,3 +1,3 @@ -var allTerrainFormsBuilder=function(Z){"use strict";class Ft{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,d=null,c=null,u=0,w=0;const b=()=>{document.removeEventListener("pointermove",S),document.removeEventListener("pointerup",I),document.removeEventListener("pointercancel",W),document.removeEventListener("keydown",z),window.removeEventListener("blur",W),d?.remove(),d=null,t.source.classList.remove("atf-is-dragging"),c?.onLeave?.(f),c=null,this.active=null,this.lastEndMs=Date.now()},f={payload:t,isFinished:()=>l,cancel:($="caller")=>{l||(l=!0,b(),e.onCancel?.($))}},x=$=>{o=!0,t.source.classList.add("atf-is-dragging");const A=t.source.getBoundingClientRect();u=t.ghost?.offsetX??a-A.left,w=t.ghost?.offsetY??i-A.top,d=t.ghost?.element??t.source.cloneNode(!0),d.classList.add("atf-drag-ghost"),d.style.width=`${A.width}px`,document.body.appendChild(d),v($)},v=$=>{d&&(d.style.transform=`translate3d(${$.clientX-u}px, ${$.clientY-w}px, 0)`)},S=$=>{if(l)return;if(!o){if(Math.hypot($.clientX-a,$.clientY-i)<4)return;x($)}v($);const A=this.hitTest($.clientX,$.clientY);A!==c&&(c?.onLeave?.(f),c=A,c?.onEnter?.(f))},I=$=>{if(l)return;if(!o){l=!0,b(),e.onClickOnly?.();return}const A=c;if(l=!0,b(),A&&A.accept(t)){e.onCommit?.(A),A.onDrop(f,{clientX:$.clientX,clientY:$.clientY});return}e.onCancel?.(A?"rejected":"no-target")},W=()=>f.cancel("pointercancel"),z=$=>{$.key==="Escape"&&f.cancel("escape")};return document.addEventListener("pointermove",S),document.addEventListener("pointerup",I),document.addEventListener("pointercancel",W),document.addEventListener("keydown",z),window.addEventListener("blur",W),this.active=f,f}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=qt(i.element);if(l>a){s=i,a=l;continue}l===a&&s&&Dt(i.element,s.element)&&(s=i)}return s}}function qt(n){let e=0,t=n;for(;t;)e++,t=t.parentElement;return e}function Dt(n,e){return(e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_FOLLOWING)!==0}function we(){return window.wp?.os??null}let xe=null;function te(){const n=we();return n?.dragManager?n.dragManager:(xe||(xe=new Ft),xe)}function Mt(n){const e=a=>{const i=a.detail?.payload;return i&&n.includes(i.type)?i.source:null},t=a=>e(a)?.classList.add("atf-is-dragging"),s=a=>e(a)?.classList.remove("atf-is-dragging");return document.addEventListener("os.drag.start",t),document.addEventListener("os.drag.end",s),()=>{document.removeEventListener("os.drag.start",t),document.removeEventListener("os.drag.end",s)}}function He(n,e,t,s,a){const i=e.getBoundingClientRect();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 Ue(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,d,{source:"allterrain-forms"}):(l,d)=>fetch(l,d))(t,{credentials:"same-origin",...e,headers:s});if(!o.ok){let l=`Request failed with status ${o.status}.`,d="";try{const c=await o.json();l=c.message??l,d=c.code??""}catch{}throw new le(l,o.status,d)}if(o.status!==204)return await o.json()}const Y=n=>_e(n);async function Nt(n){if(!B?.wpRestUrl)throw new le("AllTerrain Forms is not configured on this page.",0);const e=B.nonce?{"X-WP-Nonce":B.nonce}:{},t=we(),s=ze(B.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 le(`Request failed with status ${a.status}.`,a.status);return await a.json()}function Rt(n){if(!n||!n.includes("&"))return n;const e=document.createElement("textarea");return e.innerHTML=n,e.value}const ee=(n,e)=>_e(n,{method:"POST",body:JSON.stringify(e)}),ce=n=>_e(n,{method:"DELETE"});function Ye(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 de(n){const e=n?.schema?.settings;return e&&(!e.themeOverrides||Array.isArray(e.themeOverrides))&&(e.themeOverrides={...e.themeOverrides}),n}const F={config:()=>Y("/config"),listForms:()=>Y("/forms"),getForm:n=>Y(`/forms/${n}`).then(de),createForm:n=>ee("/forms",n).then(de),updateForm:(n,e)=>ee(`/forms/${n}`,e).then(de),duplicateForm:n=>ee(`/forms/${n}/duplicate`,{}).then(de),deleteForm:n=>ce(`/forms/${n}`),preview:(n,e)=>ee(`/forms/${n}/preview`,e),pages:()=>Nt("wp/v2/pages?per_page=100&status=publish&orderby=title&order=asc&_fields=id,title").then(n=>n.map(e=>({id:e.id,title:Rt(e.title?.rendered??"")||`#${e.id}`}))),mergeTags:n=>Y(`/forms/${n}/merge-tags`).then(e=>e.groups),analytics:(n,e="")=>Y(`/forms/${n}/analytics${e?`?dimension=${encodeURIComponent(e)}`:""}`),demoStatus:()=>Y("/demo"),demoSeed:n=>ee("/demo",n?{count:n}:{}),demoRemove:()=>ce("/demo"),listEntries:n=>Y(`/entries${Ye(n)}`),getEntry:n=>Y(`/entries/${n}`),updateEntry:(n,e)=>ee(`/entries/${n}`,e),deleteEntry:n=>ce(`/entries/${n}`),exportEntries:n=>Y(`/entries/export${Ye(n)}`),listThemes:()=>Y("/themes"),saveTheme:n=>ee("/themes",n),deleteTheme:n=>ce(`/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 j(n){return r("span",{class:`dashicons ${n.startsWith("dashicons-")?n:`dashicons-${n}`}`,attrs:{"aria-hidden":"true"}})}function m(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 L(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(H("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 ue(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 M(n,e,t){if(H("os-select")&&H("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 q(n,e,t){if(H("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 C(n,e,t="secondary",s){const a=[s?j(s):null,r("span",{text:n})];if(H("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 H(n){return typeof customElements<"u"&&!!customElements.get(n)}const Pt=["os-button","os-checkbox-label","os-number-field","os-select","os-option","os-segmented","os-segment","os-color-field","os-empty-state"];let he=null;function Bt(){if(he)return he;const n=window.wp?.os;return he=n?.loadComponents?n.loadComponents(Pt).catch(()=>{}):Promise.resolve(),he}async function ke(n,e=""){const t=window.wp?.os;return t?.confirm?t.confirm({title:e,message:n,danger:!0}):window.confirm(n)}function U(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 jt(n){let e=0,t;return(...s)=>{t=s,!e&&(e=window.requestAnimationFrame(()=>{e=0,n(...t)}))}}function Se(n,e){let t=0;return(...s)=>{window.clearTimeout(t),t=window.setTimeout(()=>n(...s),e)}}function Wt(n){try{return window.localStorage.getItem(n)??""}catch{return""}}function Ht(n,e){try{window.localStorage.setItem(n,e)}catch{}}function Ge(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 Xe(){const n=[window];try{window.parent&&window.parent!==window&&n.push(window.parent)}catch{}return n}function Ut(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 zt=4e3,Yt=100;function Gt(n,e){const t=Date.now()+zt,s=()=>{const a=Ut(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&&Zt(t);for(const a of Xe())if(a.wp?.os?.openWindow?.(e,{source:"handoff",params:s})){window.setTimeout(()=>Gt(a,e),0);return}}function Kt(){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 Xe())if(s.wp?.os?.openWindow?.(t,{source:"handoff"}))return})}const Vt="allterrain-forms/requested-form";function Zt(n){try{window.sessionStorage.setItem(Vt,String(n))}catch{}}function Ke(n){return`allterrain-forms/requested-form-${n}`}function Jt(n){try{const e=window.sessionStorage.getItem(Ke(n));return window.sessionStorage.removeItem(Ke(n)),Number(e)||0}catch{return 0}}const Le={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"},Ee=["empty","not_empty"];function Ve(n,e){const t=n.find(s=>s.id===e);return t?t.label||"an untitled question":null}function Ze(n,e,t){return n.find(i=>i.id===e)?.choices?.find(i=>i.value===t)?.label||t}function Qt(n,e){const t=Le[n.operator]??String(n.operator);if(Ee.includes(n.operator))return t;const s=Ze(e,n.field,n.value);return`${n.operator==="is"?"":`${t} `}${s!==""?s:"(nothing)"}`}function Je(n,e,t=0){const s=Ve(e,n.field),a=Le[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(Ee.includes(n.operator))return i;const o=Ze(e,n.field,n.value);return i.push({kind:"value",text:o!==""?o:"(nothing)",raw:n.value,sourceId:n.field,ruleIndex:t}),i}function es(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(...Je(a,e,i))}),s}function Qe(n){return n.map(e=>e.text).join(" ")}function ts(n,e){return Qe(Je(n,e))}function pe(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:ts(a,n),short:Qt(a,n),action:s.action,broken:Ve(n,a.field)===null})}return e}function ss(n){const e=new Map,t=new Set;for(const s of pe(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 et=10,tt=22,se="http://www.w3.org/2000/svg";class ns{constructor(e){this.edges=[],this.teardowns=[],this.frame=0,this.host=e,this.svg=document.createElementNS(se,"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,d=s.right-a.left,c=s.top+s.height/2-a.top,u=Math.max(0,a.width-et-Math.max(o,d)),w=22+i*11+Math.min(22,Math.abs(c-l)/8),b=Math.max(12,Math.min(w,u*.4)),f=document.createElementNS(se,"path");f.setAttribute("d",`M ${o} ${l} C ${o+b} ${l}, ${d+b} ${c}, ${d} ${c}`),f.setAttribute("class",`atfb-logicmap__path is-${e.action}${e.broken?" is-broken":""}`),f.dataset.from=e.from,f.dataset.to=e.to;const x=document.createElementNS(se,"title");x.textContent=e.label,f.append(x);const v=document.createElementNS(se,"circle");v.setAttribute("cx",String(d)),v.setAttribute("cy",String(c)),v.setAttribute("r","3.5"),v.setAttribute("class",`atfb-logicmap__dot is-${e.action}${e.broken?" is-broken":""}`),v.dataset.from=e.from,v.dataset.to=e.to,this.svg.append(f,v);const S=document.createElementNS(se,"text");S.setAttribute("x",String(a.width-et)),S.setAttribute("y",String(c+3)),S.setAttribute("text-anchor","end"),S.setAttribute("class","atfb-logicmap__label"),S.dataset.from=e.from,S.dataset.to=e.to,S.textContent=e.short.length>tt?`${e.short.slice(0,tt-1)}…`:e.short;const I=document.createElementNS(se,"title");I.textContent=e.label,S.append(I),this.svg.append(S)}}const as={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:"summary",color:"text",name:"composite",address:"composite",total:"text",hidden:"static",heading:"static",html:"static",divider:"static",spacer:"static",page_break:"static"};function is(n){return as[n]??"text"}function st(n,e){const t=/^choice:(\d+):(label|value)$/.exec(e);return String(t?n.choices?.[Number(t[1])]?.[t[2]]??"":n[e]??"")}function J(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 rs(n){const e=r("input",{class:"atf-choice__input",type:n==="checkboxes"||n==="multiselect"?"checkbox":"radio"});return e.disabled=!0,e}function os(n,e,t){const s=is(n.type),a=J({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,cs(n,e,s,t),ls(n,e,t)];return r("div",{class:"atfb-preview atf-form",children:[r("div",{class:`atf-field atf-field--${n.type}`,children:i})]})}function ls(n,e,t){if(!e?.supports.includes("hint"))return null;const s=J({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 cs(n,e,t,s){switch(t){case"text":return Ce(n,"atf-input",s);case"textarea":return Ce(n,"atf-input atf-textarea",s,!0);case"select":return r("div",{class:"atfb-preview__select",children:[Ce(n,"atf-input atf-select",s)]});case"options":return us(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})(),J({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 hs(n,e,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.`}),n.type==="repeater"?ds(n,s):null]})}}function Ce(n,e,t,s=!1){const a=J({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 $e(n,e,t,s,a,i){return r("span",{class:`atf-button ${t} atfb-preview__button`,children:[J({value:n,placeholder:e,class:"atfb-preview__button-text",bind:s,onInput:o=>a.edit(l=>i(l,o))})]})}function ds(n,e){return $e(String(n.addLabel??""),"Add another","atf-button--secondary","addLabel",e,(t,s)=>{t.addLabel=s})}function us(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:[rs(n.type),J({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 d=!l.value||l.value===l.label;l.label=i,d&&(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 hs(n,e,t){return n.type==="heading"?J({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:[J({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:[$e(String(n.prevLabel??""),"Back","atf-button--secondary","prevLabel",t,(s,a)=>{s.prevLabel=a}),$e(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 Ae=new Map;function Te(n){let e=Ae.get(n);return e||(e=F.mergeTags(n).catch(()=>[]),Ae.set(n,e)),e}function ps(n){Ae.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 ms(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 bs(n){return/\{[a-z_]+(?::[^}]*)?\}/i.test(n)}let Q=null;function ae(){Q?.remove(),Q=null}typeof document<"u"&&(document.addEventListener("pointerdown",n=>{const e=n.target;e?.closest(".atfb-tagpick__open")||Q&&!Q.contains(e)&&ae()}),document.addEventListener("keydown",n=>{n.key==="Escape"&&Q&&(ae(),n.stopPropagation())}));function nt(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 gs(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 vs(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 d of n){const c=d.items.filter(u=>!o||u.label.toLowerCase().includes(o)||u.tag.toLowerCase().includes(o));if(!c.length){d.empty&&!o&&!d.items.length&&s.append(r("p",{class:"atfb-tagpick__group",text:d.label}),r("p",{class:"atfb-tagpick__empty",text:d.empty}));continue}s.append(r("p",{class:"atfb-tagpick__group",text:d.label}));for(const u of c)l+=1,s.append(r("button",{class:"atfb-tagpick__item",type:"button",on:{click:()=>{e(u.tag),ae()}},children:[r("span",{class:"atfb-tagpick__item-main",children:[r("span",{class:"atfb-tagpick__label",text:u.label}),r("code",{class:"atfb-tagpick__tag",text:u.tag})]}),u.hint||u.sample?r("span",{class:"atfb-tagpick__meta",text:u.sample?`e.g. ${u.sample}`:u.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 at(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:[j("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(!bs(n.value)){a.textContent="",a.hidden=!0;return}Te(e.formId).then(o=>{a.hidden=!1,a.replaceChildren(r("span",{class:"atfb-taggable__preview-label",text:"Reads as"}),r("span",{text:ms(n.value,o)}))})}};return n.addEventListener("input",i),i(),t.addEventListener("click",o=>{if(o.stopPropagation(),Q&&s.contains(Q)){ae();return}ae(),Te(e.formId).then(l=>{const d=vs(l,c=>{nt(n,c),i()});s.append(d),Q=d,(window.innerHeight-d.getBoundingClientRect().topgs(s))&&d.classList.add("atfb-tagpick--above"),d.querySelector(".atfb-tagpick__search")?.focus()})}),s}function it(n,e){const t=parseFloat(String(n??""));return Number.isFinite(t)?t:e}function rt(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:Ie.map(({value:n,label:e})=>({value:n,label:e})),owns:["radius-field","radius-button","radius-card","radius-check"],read:n=>rt(it(n["radius-field"],4),Ie),apply:n=>{const e=Ie.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:Oe.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=>rt(it(n["pad-field-y"],9),Oe),apply:n=>{const e=Oe.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:ys,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:ws,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:xs,owns:["label-position"],read:n=>n["label-position"]??"top",apply:n=>({"label-position":n})}]}function _s(n){return Fe().find(e=>e.owns.includes(n))??null}function ot(n){let e=n.activeSlug,t={...n.overrides},s=n.themes;const a=h=>{t=h,n.onOverridesReplaced?.({...h})},i=r("div",{class:"atfs-preview__frame"}),o=r("div",{class:"atfs-controls__body"}),l=r("div",{class:"atfs-quick"}),d=r("div",{class:"atfs-themes"}),c=Se(async()=>{try{const h=await n.previewFor(e,t),p=i.closest(".atf-studio__preview"),g=p?.scrollTop??0;i.innerHTML=h,p&&(p.scrollTop=g),document.dispatchEvent(new CustomEvent("atf-refresh"))}catch(h){R(i),i.append(r("p",{class:"atfb-error",text:h instanceof Error?h.message:"Preview failed."}))}},180),u={"label-position":"atf-labels-","field-style":"atf-fields-"},w=(h,p,g)=>{const y=g.replace(/[^a-z0-9_-]/gi,"");for(const _ of[...h.classList])_.startsWith(p)&&h.classList.remove(_);y&&h.classList.add(p+y)},b=h=>{const p=i.querySelector(".atf-form-wrap"),g=p?.querySelector(".atf-form");if(p)for(const[y,_]of Object.entries(h))_===""?p.style.removeProperty(`--atf-${y}`):p.style.setProperty(`--atf-${y}`,_),g&&u[y]&&w(g,u[y],_||(s.find(O=>O.slug===e)?.resolved[y]??""))},f=h=>{b(h.resolved);const p=i.querySelector(".atf-form");p&&(w(p,"atf-theme-",h.slug),p.classList.toggle("atf-is-dark",!!h.dark))},x=h=>t[h.token]!==void 0?t[h.token]:s.find(g=>g.slug===e)?.resolved?.[h.token]??h.default,v=()=>{R(d);for(const h of s){const p=r("button",{class:`atfs-theme${h.slug===e?" is-active":""}`,type:"button",attrs:{"aria-pressed":h.slug===e,title:h.description},children:[r("span",{class:"atfs-theme__chip",style:{background:h.resolved.surface??"#fff",borderColor:h.resolved.border??"#ccc",borderRadius:h.resolved["radius-field"]??"4px",boxShadow:h.resolved["shadow-card"]??"none"},children:[r("span",{class:"atfs-theme__accent",style:{background:h.resolved.accent??"#2271b1",borderRadius:h.resolved["radius-button"]??"4px"}})]}),r("span",{class:"atfs-theme__name",text:h.label}),h.custom?r("span",{class:"atfb-badge",text:"yours"}):null],on:{click:()=>{e=h.slug,a({}),n.onTheme(e),f(h),v(),z(),W(),Tt()}}});d.append(p)}},S=()=>{const p={...s.find(g=>g.slug===e)?.resolved??{}};for(const[g,y]of Object.entries(t))p[g]=y;return p},I=h=>{const p=l.closest(".atf-studio__controls"),g=p?.scrollTop??0;h(),p&&(p.scrollTop=g)},W=Se(()=>I(k),150),z=()=>{const h=S(),p=l.querySelectorAll(".atfs-dial");Fe().forEach((g,y)=>{const _=p[y];if(!_)return;const O=g.read(h);if(g.kind==="colour"){const T=_.querySelector('input[type="color"]'),X=_.querySelector("input.atfb-input");T&&document.activeElement!==T&&(T.value=fe(O)),X&&document.activeElement!==X&&(X.value=O);return}_.querySelectorAll(".atfs-segment").forEach((T,X)=>{const V=g.steps?.[X]?.value===O;T.classList.toggle("is-on",V),T.setAttribute("aria-pressed",String(V))})})},$=(h,p)=>{const g=h.apply(p,S());for(const[y,_]of Object.entries(g))t[y]=_,n.onOverride(y,_);b(g),z(),W()},A=()=>{R(l);const h=S();for(const p of Fe()){const g=p.read(h);let y;if(p.kind==="colour"){const _=r("input",{class:"atfs-color",type:"color",value:fe(g),attrs:{"aria-label":p.label},on:{input:O=>$(p,O.target.value)}});y=r("div",{class:"atfs-color-row",children:[_,L(g,O=>$(p,O))]})}else y=r("div",{class:`atfs-segmented atfs-segmented--${p.kind}`,attrs:{role:"group","aria-label":p.label},children:(p.steps??[]).map(_=>r("button",{class:`atfs-segment${_.value===g?" is-on":""}`,type:"button",text:_.label,attrs:{"aria-pressed":_.value===g},on:{click:()=>$(p,_.value)}}))});l.append(r("div",{class:"atfs-dial",children:[r("div",{class:"atfs-dial__head",children:[r("span",{class:"atfs-dial__label",text:p.label}),r("span",{class:"atfs-dial__hint",text:p.hint})]}),y]}))}},k=()=>{R(o);const h=new Map;for(const g of n.tokens){const y=h.get(g.group)??[];y.push(g),h.set(g.group,y)}const p={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[g,y]of h)o.append(r("details",{class:"atfs-group",attrs:{open:g==="colour"},children:[r("summary",{text:p[g]??g}),...y.map(_=>D(_))]}))},D=h=>{const p=x(h),g=T=>{T===""?delete t[h.token]:t[h.token]=T,n.onOverride(h.token,T),b({[h.token]:T})};let y;if(h.control==="color"){const T=r("input",{class:"atfs-color",type:"color",value:fe(p),attrs:{"aria-label":`${h.label} colour`},on:{input:V=>{const oe=V.target.value;X.value=oe,g(oe)}}}),X=L(p,V=>{T.value=fe(V),g(V)});y=r("div",{class:"atfs-color-row",children:[T,X]})}else if(h.control==="select")y=M(p,(h.options??[]).map(T=>({value:T,label:T})),g);else if(h.control==="length"){const T=parseFloat(p)||0,X=h.unit??"px",V=h.token.includes("radius")?60:80,oe=r("input",{class:"atfs-range",type:"range",value:String(T),attrs:{min:"0",max:String(V),step:"1","aria-label":h.label},on:{input:ye=>{const Ot=`${ye.target.value}${X}`;It.value=Ot,g(Ot)}}}),It=L(p,ye=>{oe.value=String(parseFloat(ye)||0),g(ye)});y=r("div",{class:"atfs-length-row",children:[oe,It]})}else y=L(p,g);const _=m(h.label,y),O=_s(h.token);return O&&(_.classList.add("is-dialled"),_.append(r("span",{class:"atfs-owned",text:O.label}))),t[h.token]!==void 0&&(_.classList.add("is-overridden"),_.append(r("button",{class:"atfs-reset",type:"button",text:"Reset",attrs:{"aria-label":`Reset ${h.label} to the theme's value`},on:{click:()=>{g(""),I(k)}}}))),_},E=async()=>{const h=s.find(y=>y.slug===e),p=h?`${h.label} (mine)`:"My theme",g=window.prompt("Name this theme",p);if(g)try{const y={};for(const O of n.tokens){const T=x(O);T!==O.default&&(y[O.token]=T)}const _=await F.saveTheme({label:g,tokens:y});s=[...s.filter(O=>O.slug!==_.slug),_],n.onThemesChanged(s),e=_.slug,a({}),n.onTheme(e),v(),z(),I(k),U("Theme saved",_.label)}catch(y){U("Could not save the theme",y instanceof Error?y.message:"","error")}},P=async()=>{const h=s.find(p=>p.slug===e);if(h?.custom&&await ke(`Delete “${h.label}”? Forms using it fall back to Clean.`,"Delete theme"))try{await F.deleteTheme(h.id),s=s.filter(g=>g.slug!==h.slug),n.onThemesChanged(s),e="clean",a({}),n.onTheme(e),v(),z(),I(k);const p=s.find(g=>g.slug===e);p&&f(p)}catch(p){U("Could not delete the theme",p instanceof Error?p.message:"","error")}},N=async()=>{const h=s.find(y=>y.slug===e),p={label:h?.label??e,tokens:{...h?.tokens??{},...t}},g=JSON.stringify(p,null," ");try{await navigator.clipboard.writeText(g),U("Theme copied","Paste it into another site to import it.")}catch{window.prompt("Copy this theme",g)}},re=()=>{const h=window.prompt("Paste a theme");if(h)try{const p=JSON.parse(h);if(!p.tokens||typeof p.tokens!="object")throw new Error("That does not look like a theme.");for(const[g,y]of Object.entries(p.tokens))t[g]=String(y),n.onOverride(g,String(y));b({...t}),z(),I(k)}catch(p){U("Could not read that theme",p instanceof Error?p.message:"","error")}},At=C("Delete",()=>void P(),"danger"),Tt=()=>{const h=s.find(p=>p.slug===e);At.hidden=!h?.custom};return v(),A(),k(),c(),Tt(),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:[C("Save as a theme",()=>void E(),"primary"),C("Export",()=>void N()),C("Import",re),At]})]}),d]}),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 qe(){const n=document.querySelector("[data-atfs-root]");if(!n||n.dataset.atfsMounted)return;n.dataset.atfsMounted="1",Ge(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={},d=o.theme;const c=()=>{R(t),t.append(ot({themes:a,tokens:s.tokens,activeSlug:d,overrides:l,standalone:!0,onTheme:u=>{d=u},onOverride:(u,w)=>{w===""?delete l[u]:l[u]=w},onOverridesReplaced:u=>{l={...u}},previewFor:async(u,w)=>{const b=await F.getForm(o.id);b.schema.settings.theme=u,b.schema.settings.themeOverrides=w;const{html:f}=await F.preview(o.id,{schema:b.schema,theme:u});return f},onThemesChanged:u=>{a.length=0,a.push(...u)}}))};e&&(R(e),e.append(r("span",{class:"atfs__label",text:"Preview against"}),M(String(o.id),i.map(u=>({value:String(u.id),label:u.title||"(untitled)"})),u=>{o=i.find(w=>String(w.id)===u)??i[0],l={},c()}))),c()}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 qe()):qe(),document.addEventListener("os-window-content-loaded",()=>void qe()));function fe(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 ks={min:-1,max:-1,sum:-1,avg:-1,round:-1,ceil:1,floor:1,abs:1,sqrt:1,pow:2},lt={"+":{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 Ss(n,e,t=[]){if(!n||!n.trim()||n.length>2e3)return null;const s=Ls(n,e,t),a=Es(s);if(!a)return null;const i=$s(a);if(!i)return null;const o=As(i);return o===null||!Number.isFinite(o)?null:o}function Ls(n,e,t){return n.replace(/\{([a-zA-Z0-9_]+)\}/g,(s,a)=>{const i=Object.prototype.hasOwnProperty.call(e,a)?e[a]:null,o=t.find(d=>d.id===a)??null,l=ct(i,o).toFixed(10).replace(/0+$/,"").replace(/\.$/,"");return l===""||l==="-"?"0":l})}function ct(n,e){if(typeof n=="boolean")return n?1:0;if(Array.isArray(n))return n.reduce((a,i)=>a+ct(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 Es(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 As(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 Is=["sum","min","max","avg","round","ceil","floor","abs","sqrt","pow"],Os=["number","range","scale","rating","total","select","multiselect","radio","checkboxes","switch","quiz"];function De(n,e){return n.filter(t=>t.id!==e&&Os.includes(t.type))}function Fs(n,e){const t={};return De(n,e).forEach((s,a)=>{t[s.id]=a+1}),t}function qs(n){const e=r("div",{class:"atfb-overlay"}),t=()=>{e.remove(),document.removeEventListener("keydown",s)},s=b=>{b.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=Fs(n.fields,n.field.id),l=()=>{const b=a.value.trim();if(b===""){i.textContent="Empty. Reference a question below to start.",i.classList.remove("is-error");return}const f=Ss(b,o,n.fields);if(f===null){i.textContent="This does not compute yet — check the braces and parentheses.",i.classList.add("is-error");return}const x=De(n.fields,n.field.id).map((v,S)=>`${v.label||v.id} = ${S+1}`).join(", ");i.textContent=`With sample answers (${x}): ${f}`,i.classList.remove("is-error")};a.addEventListener("input",l);const d=(b,f,x=0)=>r("button",{class:"atfb-formula__chip",type:"button",text:b,on:{click:()=>{if(nt(a,f),x>0){const v=(a.selectionStart??a.value.length)-x;a.setSelectionRange(v,v)}}}}),c=De(n.fields,n.field.id),u=r("div",{class:"atfb-formula__chips",children:c.length?c.map(b=>d(b.label||b.id,`{${b.id}}`)):[r("p",{class:"atfb-hint",text:"No number-shaped questions yet — add a number, scale or priced choice field and it appears here."})]}),w=r("div",{class:"atfb-formula__chips",children:Is.map(b=>d(`${b}()`,`${b}()`,1))});e.append(r("div",{class:"atfb-modal atfb-formula",attrs:{role:"dialog","aria-label":"Formula editor"},children:[r("h2",{text:"Formula"}),a,i,m("Your questions",u,"Click one to reference its answer."),m("Functions",w),r("div",{class:"atfb-modal__actions",children:[C("Cancel",t),C("Save formula",()=>{n.onSave(a.value.trim()),t()},"primary")]})]})),e.addEventListener("click",b=>{b.target===e&&t()}),document.addEventListener("keydown",s),n.root.append(e),l(),a.focus(),a.setSelectionRange(a.value.length,a.value.length)}const Ds="allterrain-forms/form";function Ms(){return window.wp?.os?.relations??null}function dt(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 Ns=6e3,ut=120;function ht(n,e){const t=Ms();if(Me.set(n,e),!t?.set)return;const s=i=>{if(me.get(n)!==a)return;const o=dt(n);if(!o){Date.now()s(i),ut);return}try{t.set(o,e)}catch(d){pt||(pt=!0,console.error("[AllTerrain Forms] The shell refused a window identity.",d,e)),me.delete(n);return}if(!e||t.get?.(o)?.id===e.id||Date.now()>=i){me.delete(n);return}window.setTimeout(()=>s(i),ut)},a=Symbol("atf-identity");me.set(n,a),s(Date.now()+Ns)}let pt=!1;const me=new WeakMap,Me=new Map;function Rs(){for(const[n,e]of Me){if(!n.isConnected){Me.delete(n);continue}ht(n,e)}}if(typeof document<"u")for(const n of["os-window-content-loaded","os-window-opened"])document.addEventListener(n,()=>Rs());function Ps(n,e){return{type:Ds,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 be={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 Bs(){return{mode:"blocks",starts:"",ends:"",contains:"",notContains:"",chars:[],minLen:"",maxLen:"",regex:"",message:"",tests:[]}}function ft(n){const e=Bs();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 be)),Array.isArray(s.tests)&&(e.tests=s.tests.filter(a=>typeof a=="string").slice(0,10)),e}function ge(n){return n.replace(/[.*+?^${}()|[\]\\/]/g,"\\$&")}function ve(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(`(?=.*${ge(n.contains)})`),n.notContains!==""&&e.push(`(?!.*${ge(n.notContains)})`),n.starts!==""&&e.push(`(?=${ge(n.starts)})`),n.ends!==""&&e.push(`(?=.*${ge(n.ends)}$)`);const a=n.chars.map(o=>be[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=>(be[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 js(n,e){const t=ve(n);if(t==="")return null;try{return new RegExp(t).test(e)}catch{return null}}function Ws(){return window.wp?.os?.windowManager??null}const bt="allterrain-forms-validation";function Hs(n){const e=Ws(),t=dt(n.root);if(e?.openChild&&t){Us(e,t,n);return}zs(n)}function Us(n,e,t){const s=`${bt}-${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:bt,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"}),d=r("div",{class:"atfb-valwin__scroll"});l.append(d),gt(d,t,a,i,"window");const c=d.querySelector(".atfb-modal__actions");c&&l.append(c),o.append(l)},onClose:()=>{a.saved||t.onCancel?.()}})}function zs(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"})]});gt(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 gt(n,e,t,s,a){const i=ft(String(e.field.validationRecipe??""));i.mode==="blocks"&&ve(i)===""&&e.field.pattern&&(i.mode="regex",i.regex=String(e.field.pattern)),i.message||(i.message=String(e.field.messages?.invalid??""));const o=(k,D)=>{const E=r("input",{class:"atfb-input",value:i[k],placeholder:D,attrs:{type:"text"}});return E.addEventListener("input",()=>{i[k]=E.value,A()}),E},l=(k,D)=>{const E=r("input",{class:"atfb-input atfb-valwin__len",value:i[k],attrs:{type:"number",min:"0","aria-label":D}});return E.addEventListener("input",()=>{i[k]=E.value,A()}),E},d=r("div",{class:"atfb-valwin__chars",children:Object.entries(be).map(([k,D])=>q(D.label,i.chars.includes(k),E=>{i.chars=E?[...i.chars,k]:i.chars.filter(P=>P!==k),A()}))}),c=r("div",{class:"atfb-valwin__pane",children:[m("Starts with",o("starts","e.g. AT-")),m("Ends with",o("ends","e.g. -2026")),m("Must contain",o("contains","e.g. @")),m("Must not contain",o("notContains","e.g. spaces? type one")),m("Only these characters",d,"Leave every box unticked to allow anything."),m("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.")]}),u=r("textarea",{class:"atfb-input atfb-valwin__regex",attrs:{rows:"2","aria-label":"Regular expression",placeholder:"^AT-[0-9]{4}$"}});u.value=i.regex,u.addEventListener("input",()=>{i.regex=u.value,A()});const w=r("div",{class:"atfb-valwin__pane",children:[m("Expression",u,"A regular expression, without slashes. Checked against the whole answer only if you anchor it with ^ and $.")]}),b=[["blocks","Easy blocks"],["regex","Expression (advanced)"]],f=k=>{i.mode=k,c.hidden=k!=="blocks",w.hidden=k!=="regex",A()},v=(()=>{if(H("os-segmented")&&H("os-segment")){const E=document.createElement("os-segmented");E.setAttribute("value",i.mode),E.setAttribute("label","How to write the rule"),E.classList.add("atfb-valwin__tabs");for(const[P,N]of b){const re=document.createElement("os-segment");re.setAttribute("value",P),re.textContent=N,E.append(re)}return E.addEventListener("os-pick",P=>{const N=P.detail?.value;(N==="blocks"||N==="regex")&&(E.setAttribute("value",N),f(N))}),E}const k=r("div",{class:"atfb-valwin__tabs",attrs:{role:"tablist"}}),D=()=>{k.replaceChildren(...b.map(([E,P])=>{const N=i.mode===E;return r("button",{class:`atfb-valwin__tab${N?" is-active":""}`,type:"button",text:P,attrs:{role:"tab","aria-selected":N?"true":"false"},on:{click:()=>{f(E),D()}}})}))};return D(),k})();c.hidden=i.mode!=="blocks",w.hidden=i.mode!=="regex";const S=r("input",{class:"atfb-input",value:i.message,placeholder:"That is not in the expected format.",attrs:{type:"text"}});S.addEventListener("input",()=>{i.message=S.value});const I=r("p",{class:"atfb-valwin__summary",attrs:{"aria-live":"polite"}}),W=r("div",{class:"atfb-valwin__samples"}),z=k=>{const D=r("span",{class:"atfb-valwin__verdict",attrs:{"aria-live":"polite"}}),E=r("input",{class:"atfb-input",value:k,placeholder:"Type a sample answer…",attrs:{type:"text"}});E.addEventListener("input",()=>A()),W.append(r("div",{class:"atfb-valwin__sample",children:[E,D]}))};for(const k of i.tests.length?i.tests:["","",""])z(k);const $=()=>Array.from(W.querySelectorAll("input")).map(k=>k.value),A=()=>{const k=mt(i);I.textContent=k||"Nothing yet — fill in a block above and the rule appears here in plain words.";for(const D of Array.from(W.querySelectorAll(".atfb-valwin__sample"))){const E=D.querySelector("input"),P=D.querySelector(".atfb-valwin__verdict");if(!E||!P)continue;const N=E.value===""?null:js(i,E.value);P.textContent=N===null?"·":N?"✓ passes":"✗ fails",P.classList.toggle("is-pass",N===!0),P.classList.toggle("is-fail",N===!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.`}),v,c,w,r("div",{class:"atfb-valwin__try",children:[r("h3",{text:"Try it out"}),I,W,C("Add another sample",()=>{z(""),A()},"ghost","plus-alt2")]}),m("When it fails, say",S,"Shown to the visitor when their answer breaks the rule. Leave empty for the default wording."),r("div",{class:"atfb-modal__actions",children:[C(a==="window"?"Close without saving":"Cancel",s),C("Save rule",()=>{const k=ve(i);i.tests=$().filter(D=>D!=="").slice(0,10),t.saved=!0,e.onSave({pattern:k,recipe:i,message:i.message.trim()}),s()},"primary")]})),A()}const Ys=["Contact","Numbers & codes","Text shape","Web"],vt=[{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 Gs(n){return vt.find(e=>e.slug===n)??null}function Ne(){return window.wp?.os??null}const yt="allterrain-forms/preview",Re="allterrain-forms-preview";function Xs(n){const e=Ne();if(!e?.registerTitleBarButton)return()=>{};const t=()=>{try{e.registerTitleBarButton({id:yt,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 wt(n),owner:"allterrain-forms-builder"})}catch{}};return e.ready?e.ready(t):t(),()=>{try{e.unregisterTitleBarButton?.(yt)}catch{}}}async function wt(n){n.isDirty()&&await n.save();const e=n.current();e&&xt(e.id,e.title,e.previewUrl)}function xt(n,e,t){const s=Ne();if(!s?.windowManager?.open){window.open(t,"_blank","noopener");return}s.windowManager.open({id:`${Re}-${n}`,baseId:Re,url:t,title:`Preview: ${e}`,icon:"dashicons-visibility"})}function Ks(n,e,t){if(!Ne()?.windowManager?.open||!document.querySelector(`[data-window-id="${Re}-${n}"]`))return;const i=t.includes("?")?"&":"?";xt(n,e,`${t}${i}atf_r=${Date.now()}`)}const ie="allterrain-forms/field",Vs=["openstation/file","desktop-mode/file","openstation/attachment"],_t="allterrain-forms/logic-map-v2";function K(n,e){return n.dataset.atfbBind=e,n}const kt={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"}},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"}}},Zs={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 St(n,e,t){const s=n[e.key],a=i=>t(e.key,i);if(e.control==="checkbox")return q(e.label,!!s,a);if(e.control==="commas"){const i=Array.isArray(s)?s:[];return m(e.label,L(i.join(", "),o=>{a(o.split(",").map(l=>l.trim().replace(/^\./,"").toLowerCase()).filter(Boolean))}),e.hint)}return e.control==="select"?m(e.label,M(String(s??""),e.options??[],a),e.hint):e.control==="textarea"?m(e.label,ue(String(s??""),a),e.hint):e.control==="number"?m(e.label,G(String(s??""),a),e.hint):m(e.label,K(L(String(s??""),a),e.key),e.hint)}function Lt(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 Et(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 Pe=[{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}"}],Js=["About the person filling it in","The date and time","About this site"],Be=(n,e)=>B?.i18n?.[n]??e;class Ct{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=Wt(_t)!=="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=Se(()=>{this.save(!0)},2500),this.renderInspector=jt(()=>{if(R(this.inspector),!this.schema||(this.root.classList.toggle("atfb--build-only-panes",this.tab!=="build"),this.tab!=="build"))return;const t=this.schema.fields.find(o=>o.id===this.selected);if(this.root.classList.toggle("atfb--has-selection",!!t),!t){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 s=this.config?.fieldTypes.find(o=>o.type===t.type),a=s?.supports??[],i=(o,l)=>{const d=this.liveField(t.id);d&&(d[o]=l,this.markDirty(),this.renderCanvas())};this.inspector.append(r("h3",{class:"atfb-inspector__title",text:s?.label??t.type}),r("p",{class:"atfb-hint",text:`Reference this field as {field:${t.id}}`})),a.includes("label")&&this.inspector.append(m("Label",K(L(t.label,o=>i("label",o)),"label"))),a.includes("placeholder")&&this.inspector.append(m("Placeholder",K(L(t.placeholder,o=>i("placeholder",o)),"placeholder"))),a.includes("hint")&&this.inspector.append(m("Hint",K(L(t.hint,o=>i("hint",o)),"hint"),"Shown under the field, and read out with it.")),a.includes("nextlabel")&&this.inspector.append(m("Next button",K(L(String(t.nextLabel??""),o=>i("nextLabel",o)),"nextLabel"),"Leave empty for “Next”.")),a.includes("prevlabel")&&this.inspector.append(m("Back button",K(L(String(t.prevLabel??""),o=>i("prevLabel",o)),"prevLabel"),"Leave empty for “Back”.")),a.includes("addlabel")&&this.inspector.append(m("Add button",K(L(String(t.addLabel??""),o=>i("addLabel",o)),"addLabel"),"Leave empty for “Add another”.")),a.includes("required")&&this.inspector.append(q("Required",t.required,o=>i("required",o))),a.includes("width")&&this.inspector.append(m("Width",M(t.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"}],o=>i("width",o)))),s?.choices&&this.inspector.append(this.renderChoicesEditor(t,i)),this.renderTypeSettings(t,s,a,i),(t.type==="total"||a.includes("formula"))&&this.inspector.append(m("Formula",r("div",{class:"atfb-formula__row",children:[L(String(t.formula??""),o=>i("formula",o)),C("Formula editor",()=>qs({root:this.root,fields:this.schema?.fields??[],field:t,onSave:o=>{i("formula",o),this.renderInspector()}}))]}),"Reference answers with braces — {f1} * {f2} + 10 — or open the editor and click them in."),m("Currency symbol",L(String(t.currency??""),o=>i("currency",o)))),this.inspector.append(this.renderValidationSection(t,a,i)),this.inspector.append(this.renderLogicSection(t)),a.includes("prefill")&&this.inspector.append(this.prefillControl(t,i)),a.includes("css")&&this.inspector.append(m("CSS class",L(t.cssClass,o=>i("cssClass",o))))}),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(Mt([ie])),this.teardowns.push(Xs({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=Jt("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=M(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),C("New",()=>void this.showTemplates(),"secondary","plus-alt2"),C("Export",()=>void this.exportForm(),"secondary","download"),C("Import",()=>void this.importForm(),"secondary","upload"),C("Preview",()=>void this.preview(),"secondary","visibility"),C("Entries",()=>this.openEntries(),"secondary","list-view"),this.logicMapButton(),this.saveButton()]}))}liveField(e){return this.schema?.fields.find(t=>t.id===e)}syncCanvas(e){const t=this.canvas.querySelector(`[data-atfb-card="${CSS.escape(e.id)}"]`);if(t)for(const s of t.querySelectorAll(".atfb-editable[data-atfb-bind]")){const a=st(e,s.dataset.atfbBind??"");s.textContent!==a&&(s.textContent=a)}}syncInspector(e){if(this.selected===e.id)for(const t of this.inspector.querySelectorAll("[data-atfb-bind]")){const s=st(e,t.dataset.atfbBind??"");t.value!==s&&("value"in t?t.value=s:t.setAttribute("value",s))}}rebindCanvas(){if(this.tab!=="build")return;const e=document.activeElement;if(e instanceof HTMLElement&&this.canvas.contains(e)){e.addEventListener("blur",()=>this.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=C(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(),U("Form imported",a.title)}catch(s){U("Could not import that file",s instanceof Error?s.message:"","error")}}),e.click()}saveButton(){const e=C("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)}ps(s.id);const a=this.forms.find(i=>i.id===s.id);a&&(a.title=s.title),Ks(s.id,s.title,s.previewUrl),e||U("Form saved",s.title)}catch(s){U(Be("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 ke("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&&ht(this.root,Ps(this.form,B?.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){U("Could not create the form",o instanceof Error?o.message:"","error")}}},children:[j(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,r("div",{class:"atfb-modal__actions",children:[C("Cancel",s)]})]})),e.addEventListener("click",i=>{i.target===e&&s()}),document.addEventListener("keydown",t),this.root.append(e),a.querySelector("button")?.focus()}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."}),C("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(`${B?.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:[j(e.icon),r("span",{text:e.label})]});return t.addEventListener("pointerdown",s=>{const a=r("div",{class:"atfb-chip atfb-chip--ghost",children:[j(e.icon),r("span",{text:e.label})]});te().start({payload:He(ie,t,{fieldType:e.type,isNew:!0},s,a),origin:s,onClickOnly:()=>this.addField(e.type)})}),t.addEventListener("click",s=>{te().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:Be("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=Pe.some(b=>b.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"}),d=b=>{if(o.replaceChildren(),l.replaceChildren(),b==="query"){const f=e.prefill.startsWith("query:")?e.prefill.slice(6):"";o.append(L(f,x=>{const v=x.trim();t("prefill",v?`query:${v}`:""),c(`query:${v}`)},"utm_source"),r("p",{class:"atfb-row__hint",text:"The name of the parameter in the link people arrive on."}))}b==="custom"&&o.append(L(e.prefill,f=>{t("prefill",f),c(f)},"myplugin:something"),r("p",{class:"atfb-row__hint",text:"For a source another plugin has added through atf_resolve_prefill."})),c(e.prefill)},c=b=>{if(l.replaceChildren(),!b)return;if(b.startsWith("query:")){const x=b.slice(6);x&&(l.textContent=`A visit to …/your-page/?${x}=abc opens the form with “abc” in it.`);return}const f=Pe.find(x=>x.value===b)?.tag;f&&Te(this.form?.id??0).then(x=>{for(const v of x)for(const S of v.items)if(S.tag===f){const I=b.startsWith("user:");if(!S.sample){l.textContent="Empty unless the visitor is signed in.";return}l.textContent=I?`Opens with “${S.sample}” for you — empty for a visitor who is not signed in.`:`Opens with “${S.sample}”.`;return}})};d(i);const u=r("select",{class:"atfb-input atfb-select",on:{change:b=>{const f=b.target.value;t("prefill",f==="query"||f==="custom"?"":f),d(f)}}});u.append(r("option",{value:"",text:"Nothing — leave it empty",attrs:{selected:i===""}}));for(const b of Js){const f=document.createElement("optgroup");f.label=b;for(const x of Pe.filter(v=>v.group===b))f.append(r("option",{value:x.value,text:x.label,attrs:{selected:x.value===i}}));u.append(f)}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"}})),u.append(w,r("option",{value:"custom",text:"Something else (advanced)",attrs:{selected:i==="custom"}})),m("Pre-fill this with",r("div",{class:"atfb-prefill",children:[u,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":Qe(t)},children:[j("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(pe(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(H("os-select")&&H("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 d=document.createElement("os-option");d.setAttribute("value",l.value),d.textContent=l.label,o.append(d)}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(Le).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(d=>d.id===t.sourceId),i=(d,c)=>this.editCondition(e.id,u=>{const w=u.rules[t.ruleIndex];w&&(w.value=d)},c,s);if(a?.choices?.length){const d=a.choices.map(c=>({value:c.value,label:c.label||c.value}));return t.raw!==""&&!a.choices.some(c=>c.value===t.raw)&&d.unshift({value:t.raw,label:t.text}),this.condSelect(t.raw,d,s,"The answer that triggers this",c=>i(c,!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",d=>{d.key==="Enter"&&(d.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=pe(this.schema?.fields??[]).length>0,t=C(this.logicMapOn?"Hide logic":"Show logic",()=>{this.logicMapOn=!this.logicMapOn,Ht(_t,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=pe(t);if(!s.length||!this.logicMapOn)return;e.classList.add("has-logicmap");const a=new ns(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(c=>c.type===e.type),a=this.selected===e.id,i=this.schema?.fields??[],o=es(e,i),l=ss(i).get(e.id)??0,d=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__grip",attrs:{"aria-hidden":"true"},children:[j("menu")]}),r("div",{class:"atfb-card__body",children:[r("div",{class:"atfb-card__head",children:[j(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]}),os(e,s,{edit:c=>{const u=this.liveField(e.id);u&&(c(u),this.markDirty(),this.syncInspector(u))},restructure:c=>{const u=this.liveField(e.id);u&&(this.snapshot(),c(u),this.markDirty(),this.renderCanvas(),this.renderInspector())}}),o.length?this.renderCondition(e,o):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))]})]});return d.addEventListener("click",c=>{c.target.closest(".atfb-card__actions")||te().recentlyEndedDrag()||this.selectField(e.id)}),d.addEventListener("keydown",c=>{if(c.key==="Enter"||c.key===" "){c.preventDefault(),this.selectField(e.id);return}c.altKey&&(c.key==="ArrowUp"||c.key==="ArrowDown")&&(c.preventDefault(),this.moveField(e.id,c.key==="ArrowUp"?t-1:t+1),window.requestAnimationFrame(()=>{this.canvas.querySelector(`[data-atfb-card="${CSS.escape(e.id)}"]`)?.focus()})),(c.key==="Delete"||c.key==="Backspace")&&(c.preventDefault(),this.deleteField(e.id))}),d.addEventListener("pointerdown",c=>{c.target.closest(".atfb-card__actions")||te().start({payload:He(ie,d,{fieldId:e.id,field:e,isNew:!1},c),origin:c})}),d}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:[j(e)]})}registerCanvasTarget(e){this.canvasTarget?.(),this.canvasTarget=null;const t=r("div",{class:"atfb-marker",attrs:{"aria-hidden":"true"}}),s=te().registerDropTarget({id:`atfb-canvas-${this.form?.id??0}`,element:e,accept:i=>i.type===ie,onEnter:()=>e.classList.add("is-dropping"),onLeave:()=>{e.classList.remove("is-dropping"),t.remove()},onDrop:(i,o)=>{e.classList.remove("is-dropping"),t.remove();const l=i.payload.data,d=l.fieldId?this.canvas.querySelector(`[data-atfb-card="${CSS.escape(l.fieldId)}"]`):null,c=Ue(e,".atfb-card",o.clientY,d??void 0);if(l.isNew&&l.fieldType){this.addField(l.fieldType,c);return}if(l.fieldId&&this.schema?.fields.some(u=>u.id===l.fieldId)){this.moveField(l.fieldId,c);return}l.field&&this.insertField({...l.field,id:""},c)}}),a=i=>{const o=i.detail;if(o?.payload?.type!==ie||!e.classList.contains("is-dropping"))return;const l=o.payload.data?.fieldId?this.canvas.querySelector(`[data-atfb-card="${CSS.escape(o.payload.data.fieldId)}"]`):null,d=o.clientY??0,c=Ue(e,".atfb-card",d,l??void 0),u=Array.from(e.querySelectorAll(".atfb-card")).filter(w=>w!==l);c>=u.length?e.append(t):u[c].before(t)};document.addEventListener("os.drag.move",a),this.canvasTarget=()=>{s(),document.removeEventListener("os.drag.move",a)}}addField(e,t){const s=this.config?.fieldTypes.find(i=>i.type===e);if(!s||!this.schema)return;const a={id:this.nextFieldId(),type:e,label:s.input?s.label:"",placeholder:"",hint:"",required:!1,width:"full",cssClass:"",default:"",choices:s.choices?[{label:"First choice",value:"first"},{label:"Second choice",value:"second"}]:[],logic:{enabled:!1,action:"show",match:"all",rules:[]},messages:{},prefill:"",...s.settings};this.insertField(a,t)}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=Et(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.schema.fields.findIndex(a=>a.id===e);if(t<0)return;const s=JSON.parse(JSON.stringify(this.schema.fields[t]));s.id=this.nextFieldId(),this.insertField(s,t+1)}async deleteField(e){if(!this.schema)return;const t=this.schema.fields.filter(a=>a.logic?.rules?.some(i=>i.field===e)),s=t.length?`Delete this field? ${t.length} other field${t.length===1?"":"s"} use it in a condition, and those conditions will stop working.`:Be("confirmDelete","Delete this? It cannot be undone.");await ke(s,"Delete field")&&(this.snapshot(),this.schema.fields=this.schema.fields.filter(a=>a.id!==e),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")}this.logicMap?.highlight(e),this.renderInspector()}nextFieldId(){const e=new Set((this.schema?.fields??[]).map(s=>s.id));let t=e.size+1;for(;e.has(`f${t}`);)t++;return`f${t}`}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=kt[i];o&&(this.inspector.append(St(e,o,a)),o.also&&this.inspector.append(St(e,{...o,key:o.also.key,label:o.also.label,hint:o.also.hint},a)))}s.includes("rows")&&e.type!=="likert"&&this.inspector.append(m("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 m("Statements",ue(s.map(a=>a.label??"").join(` -`),a=>t("rows",Lt(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=>q(l.label,i.includes(l.key),d=>{const c=a.map(u=>u.key).filter(u=>u===l.key?d:i.includes(u));s("parts",c.length?c:[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"?q("On by default",!!e.default,i=>s("default",i)):t?.choices&&(e.choices??[]).length?m("Default answer",M(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):m("Default answer",L(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 d=r("div",{class:"atfb-choice-row",children:[e.type==="image_choice"?this.choiceImageWell(o,l,e):null,K(L(o.label,c=>{const u=a(l);if(!u)return;const w=!u.value||u.value===u.label;u.label=c,w&&(u.value=c),this.markDirty();const b=this.liveField(e.id);b&&this.syncCanvas(b)}),`choice:${l}:label`),K(L(o.value,c=>{const u=a(l);u&&(u.value=c,this.markDirty())},"value"),`choice:${l}:value`),e.type==="quiz"||o.points!==void 0?G(String(o.points??""),c=>{const u=a(l);u&&(u.points=c===""?void 0:Number(c),this.markDirty())}):G(String(o.price??""),c=>{const u=a(l);u&&(u.price=c===""?void 0:Number(c),this.markDirty())}),r("button",{class:"atfb-card__action",type:"button",attrs:{"aria-label":`Remove ${o.label}`},on:{click:()=>{const c=this.liveField(e.id);c&&((c.choices??[]).splice(l,1),t("choices",c.choices),this.renderInspector())}},children:[j("trash")]})]});i.append(d)}),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,C("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"?m("Correct answer",M(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}`}):j("format-image")]}),i=te().registerDropTarget({id:`atfb-well-${s.id}-${t}`,element:a,accept:o=>Vs.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,d=Number(l.attachmentId??l.id??l.file?.id??0);if(!d){U("That is not an image this field can use","","error");return}const c=this.liveField(s.id)?.choices?.[t];c&&(c.image=d,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,d]of i)a.push(m(d,L(String(e[l]??""),c=>s(l,c))));t.includes("pattern")&&a.push(...this.renderAnswerShapeRows(e,s)),t.includes("unique")&&a.push(q("No two people may submit the same value",!!e.unique,l=>s("unique",l)));const o=e.messages??{};return t.includes("required")&&a.push(m("Message when required",L(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"}]},...Ys.map(i=>({heading:i,options:vt.filter(o=>o.group===i).map(o=>({value:o.slug,label:o.label}))})),{heading:"Your own",options:[{value:"custom",label:"A custom rule…"}]}];if(H("os-select")&&H("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 d=document.createElement("os-option");d.setAttribute("value",l.value),d.textContent=l.label,i.append(d)}}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=()=>Hs({root:this.root,field:this.liveField(e.id)??e,onSave:u=>{t("validation","custom"),t("pattern",u.pattern),t("validationRecipe",JSON.stringify(u.recipe));const w={...this.liveField(e.id)?.messages??{}};w.invalid=u.message,t("messages",w),this.renderInspector()},onCancel:()=>this.renderInspector()}),o=u=>{if(u==="custom"){i();return}t("validation",u),t("pattern",""),t("validationRecipe",""),this.renderInspector()},l=this.buildShapePicker(a,o),d=Gs(a),c=[m("The answer should be",l,d?`e.g. ${d.example}`:"Checked as they type, and again on the server.")];if(a==="custom"){const u=ft(String(e.validationRecipe??"")),w=mt(u)||(e.pattern?`Matches the expression ${String(e.pattern)}`:"No rule yet — open the builder.");c.push(m("Your rule",r("div",{class:"atfb-valrule",children:[r("p",{class:"atfb-valrule__words",text:w}),C("Edit the rule…",i,"secondary","edit")]}),ve(u)||e.pattern?"Built in the rule builder, with a playground to test it.":void 0))}return c}renderLogicSection(e){const t=e.logic,s=()=>this.liveField(e.id)?.logic,a=(c,u=!1)=>{const w=s();w&&(c(w),this.markDirty(),this.renderCanvas(),u&&this.renderInspector())},i=(this.schema?.fields??[]).filter(c=>c.id!==e.id&&c.type!=="page_break"),o=()=>r("div",{class:"atfb-rule-join",children:[r("button",{class:"atfb-rule-join__chip",type:"button",text:t.match==="all"?"and":"or",title:"Switch between needing every rule (and) or any one of them (or).",on:{click:()=>a(c=>{c.match=c.match==="all"?"any":"all"},!0)}})]}),l=(c,u)=>{const w=r("button",{class:"atfb-card__action",type:"button",attrs:{"aria-label":"Remove this rule"},title:"Remove this rule",on:{click:()=>a(f=>{f.rules.splice(u,1)},!0)},children:[j("trash")]}),b=[r("div",{class:"atfb-rule__top",children:[M(c.field,i.map(f=>({value:f.id,label:f.label||f.id})),f=>a(x=>{const v=x.rules[u];v&&(v.field=f,v.value="")},!0)),w]}),M(c.operator,Object.entries(this.config?.operators??{}).map(([f,x])=>({value:f,label:x})),f=>a(x=>{const v=x.rules[u];v&&(v.operator=f)},!0))];if(!Ee.includes(c.operator)){const f=this.schema?.fields.find(x=>x.id===c.field);if(f?.choices?.length){const x=f.choices.map(v=>({value:v.value,label:v.label||v.value}));c.value!==""&&!f.choices.some(v=>v.value===c.value)&&x.unshift({value:c.value,label:c.value}),b.push(M(c.value,x,v=>a(S=>{const I=S.rules[u];I&&(I.value=v)})))}else b.push(L(c.value,x=>a(v=>{const S=v.rules[u];S&&(S.value=x)}),"The answer to compare against"))}return r("div",{class:"atfb-rule",children:b})},d=r("div",{class:"atfb-rules"});return t.rules.forEach((c,u)=>{u>0&&d.append(o()),d.append(l(c,u))}),this.section(`logic:${e.id}`,"Conditional logic",[q("Only show this field sometimes",t.enabled,c=>{a(u=>{u.enabled=c},!0)}),t.enabled?r("div",{children:[r("div",{class:"atfb-rule-head",children:[M(t.action,[{value:"show",label:"Show"},{value:"hide",label:"Hide"}],c=>a(u=>{u.action=c},!0)),r("span",{text:"this field when"}),M(t.match,[{value:"all",label:"all"},{value:"any",label:"any"}],c=>a(u=>{u.match=c},!0)),r("span",{text:"of these match:"})]}),d,C("Add rule",()=>{a(c=>{c.rules.push({field:i[0]?.id??"",operator:"is",value:""})},!0)},"ghost","plus-alt2")]}):null],t.enabled)}renderTabCanvas(){return this.schema?this.tab==="theme"?ot({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=/