From c03cc560a5e98d5875db2289c5ffc4b7eeee32cf Mon Sep 17 00:00:00 2001 From: prismiwi2015 Date: Thu, 20 Aug 2026 01:42:46 +0200 Subject: [PATCH] Forms becomes a real app inside WP Explorer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Built on the Explorer's new surface (registerEntityKind, detail-route dispatch, preview-action contexts, per-section editAction): The section renders as the file manager around it renders things — icon tiles in a grid, searchable, each wearing its vitals — and a form double-clicks open into a folder whose breadcrumb is the shell's own: Site › Forms › Team pulse survey. Inside, the hierarchy deliberately stops: the form tile opens the builder, Entries and Report are doors to their windows, and the rest of the folder is the live front end at full size, because looking at a form is what a file manager's quick look is for and the space was sitting there unused. The list pane keeps the half-scale thumbnail, the stat cards and the action row. Deep links stopped racing: the requested-form store grew per-surface keys (builder, entries, analytics), every deep link writes the store before opening — the window's own start() consumes it cold — and warm windows take an immediate event. The analytics flash, where the right report rendered and start() stomped it back to the first form, is gone with the unconditional default that caused it. Three rendering truths repaired along the way. Site themes colour bare elements (label { color: … }) and beat inheritance, so everything form.css did not colour explicitly arrived slate-on-dark — an inheritance shield outranks them now, with button deliberately absent because the submit colours itself. Dark themes drew controls with borders their own surfaces swallowed (Neon 1.39:1) — Neon, Midnight and Terminal borders now clear 3:1. And the dark canvas stopped forcing #f0f0f1 where the theme's own text token belongs. The builder's narrow window earns its inspector back: one rail's worth of room follows the work — palette while adding, inspector the moment a field is selected — via an atfb--has-selection class the container queries read. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_0119U4sRRWGcTQdwYreTwpAp --- assets/css/builder.css | 28 +- assets/css/explorer.css | 337 +++++++++++++++ assets/css/form.css | 13 + assets/js/analytics.js | 36 +- assets/js/analytics.min.js | 2 +- assets/js/builder.js | 33 +- assets/js/builder.min.js | 6 +- assets/js/dock.js | 434 +++++++++++++++++++- assets/js/dock.min.js | 2 +- assets/js/entries.js | 34 +- assets/js/entries.min.js | 2 +- includes/assets.php | 7 + includes/openstation-explorer.php | 13 +- includes/openstation.php | 2 + includes/themes.php | 6 +- src/analytics.ts | 35 +- src/builder.ts | 38 +- src/dock.ts | 42 +- src/entries.ts | 29 +- src/explorer-kind.ts | 312 ++++++++++++++ src/handoff.ts | 59 +++ tests/phpunit/tests/openstationExplorer.php | 4 +- 22 files changed, 1439 insertions(+), 35 deletions(-) create mode 100644 assets/css/explorer.css create mode 100644 src/explorer-kind.ts diff --git a/assets/css/builder.css b/assets/css/builder.css index 74067aa..d4654b1 100644 --- a/assets/css/builder.css +++ b/assets/css/builder.css @@ -236,6 +236,22 @@ .atfb__inspector { display: none; } + + /* One rail's worth of room, so the rail follows the work: the palette + while adding, the inspector the moment a field is selected — otherwise + a narrow window could select a field and have nowhere to edit it. */ + .atfb--has-selection .atfb__body { + grid-template-columns: minmax( 220px, 260px ) 1fr; + } + + .atfb--has-selection .atfb__palette { + display: none; + } + + .atfb--has-selection .atfb__inspector { + display: block; + order: -1; + } } @container ( max-width: 640px ) { @@ -247,6 +263,12 @@ .atfb__palette { display: none; } + + /* Even here a selected field keeps its editor: the inspector takes the + rail back and the canvas keeps the rest. */ + .atfb--has-selection .atfb__body { + grid-template-columns: minmax( 200px, 240px ) 1fr; + } } .atfb__palette, @@ -1306,7 +1328,11 @@ .atf-block-preview:has( .atf-is-dark ), .atfb--dark-form .atfb-preview { background: #101418; - color: #f0f0f1; + /* The theme's own text token, not a hardcoded white: forcing #f0f0f1 made + the canvas render choice labels brighter than the real front end does, + and a canvas that flatters the theme is lying about it. The fallback + only speaks when a preview carries no tokens at all. */ + color: var( --atf-text, #f0f0f1 ); } /* The pane around the frame, so the dark preview is not a bright card floating diff --git a/assets/css/explorer.css b/assets/css/explorer.css new file mode 100644 index 0000000..17ca95e --- /dev/null +++ b/assets/css/explorer.css @@ -0,0 +1,337 @@ +/** + * The Forms section inside WP Explorer. + * + * Rendered by `src/explorer-kind.ts` into the Explorer window's body, so + * everything reads the shell's UI tokens the way the builder's own panes do — + * the section should look like the window it lives in, not like a visitor. + */ + +.atfx { + display: grid; + grid-template-columns: minmax( 240px, 320px ) minmax( 0, 1fr ); + gap: 0; + block-size: 100%; + min-block-size: 0; + overflow: hidden; +} + +.atfx-side { + display: flex; + flex-direction: column; + gap: 10px; + padding: 14px; + border-inline-end: 1px solid var( --os-ui-border, #dcdcde ); + overflow-y: auto; + min-block-size: 0; +} + +.atfx-search { + width: 100%; + padding: 8px 10px; + font: inherit; + border: 1px solid var( --os-ui-border, #dcdcde ); + border-radius: 8px; + background: var( --os-ui-surface, #fff ); + color: inherit; +} + +.atfx-list { + display: flex; + flex-direction: column; + gap: 6px; +} + +.atfx-tile { + display: flex; + flex-direction: column; + gap: 2px; + padding: 10px 12px; + font: inherit; + text-align: start; + color: inherit; + background: var( --os-ui-surface, #fff ); + border: 1px solid var( --os-ui-border, #dcdcde ); + border-radius: 10px; + cursor: pointer; +} + +.atfx-tile:hover { + border-color: var( --os-ui-accent, #2271b1 ); +} + +.atfx-tile.is-selected { + border-color: var( --os-ui-accent, #2271b1 ); + box-shadow: 0 0 0 1px var( --os-ui-accent, #2271b1 ); +} + +.atfx-tile__vitals { + font-size: 12px; + color: var( --os-ui-muted, #646970 ); +} + +.atfx-pane { + padding: 18px 20px; + overflow-y: auto; + min-block-size: 0; +} + +.atfx-title { + margin: 0 0 12px; + font-size: 18px; +} + +.atfx-cards { + display: grid; + grid-template-columns: repeat( auto-fit, minmax( 110px, 1fr ) ); + gap: 10px; + margin-block-end: 14px; +} + +.atfx-card { + display: flex; + flex-direction: column; + gap: 2px; + padding: 12px 14px; + border: 1px solid var( --os-ui-border, #dcdcde ); + border-radius: 10px; + background: var( --os-ui-surface, #fff ); +} + +.atfx-card__value { + font-size: 20px; + line-height: 1.2; +} + +.atfx-card__label { + font-size: 12px; + color: var( --os-ui-muted, #646970 ); +} + +/* The live preview at half size: the iframe is laid out at 200% of the slot in + both axes and scaled back by half, so the page inside believes in a full + desktop viewport while occupying a card. Pointer events stay off — this is a + preview, and a click that navigated a hidden iframe would be a haunting. */ +.atfx-preview { + margin-block: 6px 14px; + border: 1px solid var( --os-ui-border, #dcdcde ); + border-radius: 10px; + overflow: hidden; + block-size: 340px; + background: #fff; +} + +.atfx-preview__frame { + inline-size: 200%; + block-size: 200%; + border: 0; + transform: scale( 0.5 ); + transform-origin: 0 0; + pointer-events: none; +} + +.atfx-shortcode code { + font-size: 12px; + padding: 4px 8px; + border-radius: 6px; + background: var( --os-ui-surface-sunken, #f0f0f1 ); + user-select: all; +} + +.atfx-empty { + color: var( --os-ui-muted, #646970 ); + padding: 8px 2px; +} + +/* -------------------------------------------------------------------------- */ +/* Icon tiles, the way the Explorer's own sections draw theirs */ +/* -------------------------------------------------------------------------- */ + +.atfx-grid { + display: grid; + grid-template-columns: repeat( auto-fill, minmax( 120px, 1fr ) ); + gap: 10px; + align-content: start; +} + +.atfx-grid--folder { + grid-template-columns: repeat( auto-fill, minmax( 150px, 1fr ) ); + margin-block: 14px; +} + +.atfx-grid .atfx-tile { + align-items: center; + text-align: center; + gap: 6px; + padding: 14px 10px 12px; +} + +.atfx-icon { + font-size: 28px; + inline-size: 28px; + block-size: 28px; + color: var( --os-ui-fg, #1d2327 ); +} + +.atfx-tile__label { + font-size: 13px; + overflow-wrap: anywhere; +} + +/* Breadcrumbs for the trail the shell cannot draw for us yet. */ +.atfx-crumbs { + display: flex; + align-items: center; + gap: 6px; + margin-block-end: 12px; + font-size: 13px; +} + +.atfx-crumb { + padding: 2px 4px; + font: inherit; + color: var( --os-ui-accent, #2271b1 ); + background: none; + border: 0; + border-radius: 4px; + cursor: pointer; +} + +.atfx-crumb.is-here { + color: inherit; + font-weight: 600; + cursor: default; +} + +.atfx-crumb__sep { + color: var( --os-ui-muted, #646970 ); +} + +.atfx-full { + grid-column: 1 / -1; + display: flex; + flex-direction: column; + padding: 16px 20px; + overflow-y: auto; + min-block-size: 0; +} + +/* Inside the folder the preview takes whatever the cards and tiles left. */ +.atfx-full .atfx-preview--full { + flex: 1 1 auto; + block-size: auto; + min-block-size: 380px; +} + +/* The folder's Preview: natural size, scrolling like a page, because quick + look means looking at the real thing. Half-scale stays for the list pane, + where the preview is a thumbnail beside the numbers. */ +.atfx-preview--full { + block-size: calc( 100% - 48px ); + min-block-size: 420px; +} + +.atfx-preview--full .atfx-preview__frame { + inline-size: 100%; + block-size: 100%; + transform: none; + pointer-events: auto; +} + +.atfx-back { + display: inline-block; + margin-block-end: 10px; + padding: 4px 10px; + font: inherit; + color: var( --os-ui-accent, #2271b1 ); + background: none; + border: 0; + border-radius: 6px; + cursor: pointer; +} + +/* Entry rows. */ +.atfx-rows { + display: flex; + flex-direction: column; + gap: 4px; +} + +.atfx-row { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 10px; + font: inherit; + text-align: start; + color: inherit; + background: none; + border: 1px solid transparent; + border-radius: 8px; + cursor: pointer; +} + +.atfx-row:hover { + border-color: var( --os-ui-border, #dcdcde ); + background: var( --os-ui-surface, #fff ); +} + +.atfx-row__title { + flex: 1 1 auto; + min-inline-size: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.atfx-row__date { + font-size: 12px; + color: var( --os-ui-muted, #646970 ); + white-space: nowrap; +} + +.atfx-dot { + inline-size: 8px; + block-size: 8px; + border-radius: 50%; + background: var( --os-ui-border, #dcdcde ); + flex: 0 0 auto; +} + +.atfx-dot--atf-unread { + background: var( --os-ui-accent, #2271b1 ); +} + +.atfx-dot--atf-spam { + background: #d63638; +} + +/* The answers list on one entry. */ +.atfx-answers { + margin: 12px 0; + display: grid; + grid-template-columns: minmax( 120px, 200px ) 1fr; + gap: 6px 14px; +} + +.atfx-answers dt { + color: var( --os-ui-muted, #646970 ); +} + +.atfx-answers dd { + margin: 0; +} + +.atfx-open { + padding: 6px 14px; + font: inherit; + border: 1px solid var( --os-ui-border, #dcdcde ); + border-radius: 8px; + background: var( --os-ui-surface, #fff ); + color: inherit; + cursor: pointer; +} + +.atfx-open:hover { + border-color: var( --os-ui-accent, #2271b1 ); + color: var( --os-ui-accent, #2271b1 ); +} diff --git a/assets/css/form.css b/assets/css/form.css index ee131b1..212cc78 100644 --- a/assets/css/form.css +++ b/assets/css/form.css @@ -27,6 +27,19 @@ container-name: atf-form; } +/* The front end is the site theme's house, and site themes routinely colour + bare elements — `label { color: #1e293b }` — which beats inheritance and + paints dark answers onto a dark form theme (a Neon radio option arrived + slate). Re-asserting inheritance outranks any element selector a site theme + can write, and everything this stylesheet colours on purpose still wins + over it with its own, more specific rule. `button` is deliberately absent: + the submit's colour rule is single-class and this shield outranked it, + which painted the button's label in body text over the accent. The scale's + chips are labels, so they stay covered. */ +.atf-form :is( label, legend, span, p, small, li, dt, dd ) { + color: inherit; +} + .atf-form { background: var( --atf-bg ); color: var( --atf-text ); diff --git a/assets/js/analytics.js b/assets/js/analytics.js index fe6fcdb..f421256 100644 --- a/assets/js/analytics.js +++ b/assets/js/analytics.js @@ -146,6 +146,18 @@ var allTerrainFormsAnalytics = function(exports) { saveTheme: (body) => post("/themes", body), deleteTheme: (id) => del(`/themes/${id}`) }; + function requestedFormKeyFor(surface) { + return `allterrain-forms/requested-form-${surface}`; + } + function takeFormFor(surface) { + try { + const value = window.sessionStorage.getItem(requestedFormKeyFor(surface)); + window.sessionStorage.removeItem(requestedFormKeyFor(surface)); + return Number(value) || 0; + } catch { + return 0; + } + } function el(tag, options = {}) { const node = document.createElement(tag); if (options.class) { @@ -374,10 +386,25 @@ var allTerrainFormsAnalytics = function(exports) { this.demoAvailable = false; } } - this.formId = this.forms[0].id; + const requested = takeFormFor("analytics"); + if (requested && this.forms.some((form) => form.id === requested)) { + this.formId = requested; + } else if (!this.formId) { + this.formId = this.forms[0].id; + } await this.load(); } /** Fetches the report for the current form and redraws. */ + /** + * Deep-link entry: report on one form. + * + * @param formId The form. + */ + async showForm(formId) { + this.formId = formId; + this.dimension = ""; + await this.load(); + } async load() { try { this.report = await api.analytics(this.formId, this.dimension); @@ -866,6 +893,13 @@ var allTerrainFormsAnalytics = function(exports) { } } let mounted = null; + document.addEventListener("atf-open-analytics-form", (event) => { + const formId = Number(event.detail?.formId ?? 0); + if (!formId) { + return; + } + void mounted?.showForm(formId); + }); function mountAnalytics() { const root = document.querySelector("[data-atfa-root]"); if (!root || root.dataset.atfaMounted) { diff --git a/assets/js/analytics.min.js b/assets/js/analytics.min.js index 3642a40..3d04f21 100644 --- a/assets/js/analytics.min.js +++ b/assets/js/analytics.min.js @@ -1 +1 @@ -var allTerrainFormsAnalytics=function(p){"use strict";function T(){return window.wp?.os??null}const c=window.allTerrainForms;class v extends Error{constructor(e,t,n=""){super(e),this.name="ApiError",this.status=t,this.code=n}}function C(s,e){return s.includes("?")?`${s}${e.replace("?","&")}`:`${s}${e}`}async function k(s,e={}){if(!c?.restUrl)throw new v("AllTerrain Forms is not configured on this page.",0);const t=C(c.restUrl,s),n={"Content-Type":"application/json",...e.headers??{}};c.nonce&&(n["X-WP-Nonce"]=c.nonce);const i=T(),r=await(i?.fetch?(l,u)=>i.fetch(l,u,{source:"allterrain-forms"}):(l,u)=>fetch(l,u))(t,{credentials:"same-origin",...e,headers:n});if(!r.ok){let l=`Request failed with status ${r.status}.`,u="";try{const h=await r.json();l=h.message??l,u=h.code??""}catch{}throw new v(l,r.status,u)}if(r.status!==204)return await r.json()}const d=s=>k(s);async function D(s){if(!c?.wpRestUrl)throw new v("AllTerrain Forms is not configured on this page.",0);const e=c.nonce?{"X-WP-Nonce":c.nonce}:{},t=T(),n=C(c.wpRestUrl,s),i=t?.fetch?await t.fetch(n,{credentials:"same-origin",headers:e},{source:"allterrain-forms"}):await fetch(n,{credentials:"same-origin",headers:e});if(!i.ok)throw new v(`Request failed with status ${i.status}.`,i.status);return await i.json()}function j(s){if(!s||!s.includes("&"))return s;const e=document.createElement("textarea");return e.innerHTML=s,e.value}const m=(s,e)=>k(s,{method:"POST",body:JSON.stringify(e)}),y=s=>k(s,{method:"DELETE"});function F(s){const e=new URLSearchParams;for(const[n,i]of Object.entries(s))i===void 0||i===""||i===!1||e.set(n,String(i));const t=e.toString();return t?`?${t}`:""}function w(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(w),createForm:s=>m("/forms",s).then(w),updateForm:(s,e)=>m(`/forms/${s}`,e).then(w),duplicateForm:s=>m(`/forms/${s}/duplicate`,{}).then(w),deleteForm:s=>y(`/forms/${s}`),preview:(s,e)=>m(`/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=>m("/demo",s?{count:s}:{}),demoRemove:()=>y("/demo"),listEntries:s=>d(`/entries${F(s)}`),getEntry:s=>d(`/entries/${s}`),updateEntry:(s,e)=>m(`/entries/${s}`,e),deleteEntry:s=>y(`/entries/${s}`),exportEntries:s=>d(`/entries/export${F(s)}`),listThemes:()=>d("/themes"),saveTheme:s=>m("/themes",s),deleteTheme:s=>y(`/themes/${s}`)};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,i]of Object.entries(e.attrs??{}))i===void 0||i===!1||t.setAttribute(n,i===!0?"":String(i));Object.assign(t.style,e.style??{});for(const[n,i]of Object.entries(e.on??{}))t.addEventListener(n,i);for(const n of e.children??[])n==null||n===!1||t.append(n);return t}function g(s){s.replaceChildren()}function P(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 i of e){const o=document.createElement("os-option");o.setAttribute("value",i.value),o.textContent=i.label,n.append(o)}return n.addEventListener("os-pick",i=>{t(String(i.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 O(s,e,t="secondary",n){const i=[null,a("span",{text:s})];if(E("os-button")){const o=document.createElement("os-button");o.setAttribute("variant",t),o.setAttribute("type","button"),o.classList.add("atfb-button",`atfb-button--${t}`),o.addEventListener("click",e);for(const r of i)r&&o.append(r);return o}return a("button",{class:`atfb-button atfb-button--${t}`,type:"button",on:{click:e},children:i})}function E(s){return typeof customElements<"u"&&!!customElements.get(s)}const I=["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 R(){if(_)return _;const s=window.wp?.os;return _=s?.loadComponents?s.loadComponents(I).catch(()=>{}):Promise.resolve(),_}async function B(s,e=""){const t=window.wp?.os;return t?.confirm?t.confirm({title:e,message:s,danger:!0}):window.confirm(s)}function $(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 U(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 N=[{key:"detractors",label:"Detractors",hint:"0–6"},{key:"passives",label:"Passives",hint:"7–8"},{key:"promoters",label:"Promoters",hint:"9–10"}],L=50;function q(s){const e=Math.floor(s.min),t=Math.ceil(s.max);if(!Number.isFinite(e)||!Number.isFinite(t)||tMath.min(o-1,Math.max(0,Math.floor((h-e)/i))),l=[];for(let h=0;h({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"}),P(this.dimension,e.map(t=>({value:t.id,label:t.label})),t=>{this.dimension=t,this.load()}))}render(){const e=this.report;if(g(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((o,r)=>Math.max(o,r.count),0),i=t.reduce((o,r)=>o+r.count,0);return this.panel("When they answered",`${i} in the last ${t.length} days · busiest day ${n}`,[a("div",{class:"atfa-timeline",children:t.map(o=>a("div",{class:`atfa-timeline__day${o.count?"":" is-empty"}`,attrs:{style:`height:${n?Math.max(2,o.count/n*100):0}%`,title:`${o.date}: ${o.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=i=>t.responses?i/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:N.map(i=>a("div",{class:`atfa-nps__band is-${i.key}`,attrs:{style:`width:${n(t[i.key])}%`,title:`${i.label} (${i.hint}): ${t[i.key]}`}}))}),a("div",{class:"atfa-nps__legend",children:N.map(i=>a("span",{class:`atfa-nps__key is-${i.key}`,children:[a("strong",{text:String(t[i.key])}),a("span",{text:` ${i.label} `}),a("small",{text:i.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 i=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"}}))]}),o=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:[i]}),a("tbody",{children:o})]})])}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 i=e.choices.reduce((o,r)=>Math.max(o,r.count),0);n.push(a("ul",{class:"atfa-bars",children:e.choices.map(o=>a("li",{class:"atfa-bars__row",children:[a("span",{class:"atfa-bars__label",text:o.label}),a("div",{class:"atfa-meter",children:[a("div",{class:"atfa-meter__fill",attrs:{style:`width:${i?o.count/i*100:0}%`}})]}),a("span",{class:"atfa-bars__value",text:`${o.count} ${e.answered?Math.round(o.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=q(t),i=n.reduce((o,r)=>Math.max(o,r.count),0);return a("div",{class:"atfa-hist",children:n.map(o=>a("div",{class:`atfa-hist__col${o.holdsMean?" is-mean":""}`,attrs:{title:`${o.label}: ${o.count}`},children:[a("span",{class:"atfa-hist__count",text:o.count?String(o.count):""}),a("div",{class:"atfa-hist__bar",attrs:{style:`height:${i?Math.max(2,o.count/i*100):0}%`}}),a("span",{class:"atfa-hist__tick",text:o.label})]}))})}demoPanel(){const e=this.demo,t=a("p",{class:"atfa-demo__status",text:this.demoSummary(e)}),n=O("Fill with demo data",async()=>{await this.seedAll(t)}),i=O("Remove demo data",async()=>{if(await B("Delete the demo survey and every entry it generated?","Remove demo data")){t.textContent="Removing…";try{const o=await f.demoRemove();$("Demo data removed",`${o.entries} entries and ${o.forms} form.`)}catch{$("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,i]})]})}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 i=await f.demoSeed();if(this.demo=i,n=0,e.textContent=`Generating… ${i.entries} of ${i.target}`,i.remaining<1){$("Demo data ready",`${i.entries} submissions.`);break}}catch{if(n+=1,n>=3){e.textContent="Stopped early. Press it again to carry on from here.",$("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 x=null;function S(){const s=document.querySelector("[data-atfa-root]");!s||s.dataset.atfaMounted||(s.dataset.atfaMounted="1",U(s),x=new G(s),x.start())}let A=!1;return document.addEventListener("atf-open-demo-panel",()=>{A=!0,window.setTimeout(()=>{x?.revealDemo(),A=!1},600)}),document.addEventListener("os-window-content-loaded",()=>{S(),A&&window.setTimeout(()=>x?.revealDemo(),400)}),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",S):S(),p.MAX_HISTOGRAM_BUCKETS=L,p.histogramBuckets=q,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 ${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}({}); diff --git a/assets/js/builder.js b/assets/js/builder.js index 578c072..775124b 100644 --- a/assets/js/builder.js +++ b/assets/js/builder.js @@ -745,6 +745,18 @@ var allTerrainFormsBuilder = function(exports) { } catch { } } + function requestedFormKeyFor(surface) { + return `allterrain-forms/requested-form-${surface}`; + } + function takeFormFor(surface) { + try { + const value = window.sessionStorage.getItem(requestedFormKeyFor(surface)); + window.sessionStorage.removeItem(requestedFormKeyFor(surface)); + return Number(value) || 0; + } catch { + return 0; + } + } const OPERATORS = { is: "is", is_not: "is not", @@ -3219,6 +3231,7 @@ var allTerrainFormsBuilder = function(exports) { return; } const field = this.schema.fields.find((candidate) => candidate.id === this.selected); + this.root.classList.toggle("atfb--has-selection", !!field); if (!field) { this.inspector.append( el("div", { @@ -3429,7 +3442,10 @@ var allTerrainFormsBuilder = function(exports) { this.renderBar(); this.renderPalette(); if (this.forms.length) { - await this.open(this.forms[0].id); + const requested = takeFormFor("builder"); + await this.open( + requested && this.forms.some((form) => form.id === requested) ? requested : this.forms[0].id + ); } else { this.renderFormsList(); } @@ -3863,6 +3879,14 @@ var allTerrainFormsBuilder = function(exports) { } /* ---------------------------------------------------------------- Forms */ /** Opens a form. */ + /** + * Deep-link entry: WP Explorer (and anything else) asks for a form by id. + * + * @param id The form to open on the canvas. + */ + async openFormById(id) { + await this.open(id); + } async open(id) { if (this.dirty && !await confirmAction("You have unsaved changes. Discard them?")) { return; @@ -5996,6 +6020,13 @@ var allTerrainFormsBuilder = function(exports) { } let mounted = null; let mountedRoot = null; + document.addEventListener("atf-open-form", (event) => { + const formId = Number(event.detail?.formId ?? 0); + if (!formId) { + return; + } + void mounted?.openFormById(formId); + }); function mountBuilder() { if (mountedRoot?.isConnected) { return; diff --git a/assets/js/builder.min.js b/assets/js/builder.min.js index 301949f..770c758 100644 --- a/assets/js/builder.min.js +++ b/assets/js/builder.min.js @@ -1,3 +1,3 @@ -var allTerrainFormsBuilder=function(U){"use strict";class vt{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,i=s.clientX,a=s.clientY;let o=!1,l=!1,c=null,d=null,h=0,_=0;const b=()=>{document.removeEventListener("pointermove",E),document.removeEventListener("pointerup",q),document.removeEventListener("pointercancel",X),document.removeEventListener("keydown",z),window.removeEventListener("blur",X),c?.remove(),c=null,t.source.classList.remove("atf-is-dragging"),d?.onLeave?.(v),d=null,this.active=null,this.lastEndMs=Date.now()},v={payload:t,isFinished:()=>l,cancel:(L="caller")=>{l||(l=!0,b(),e.onCancel?.(L))}},x=L=>{o=!0,t.source.classList.add("atf-is-dragging");const O=t.source.getBoundingClientRect();h=t.ghost?.offsetX??i-O.left,_=t.ghost?.offsetY??a-O.top,c=t.ghost?.element??t.source.cloneNode(!0),c.classList.add("atf-drag-ghost"),c.style.width=`${O.width}px`,document.body.appendChild(c),k(L)},k=L=>{c&&(c.style.transform=`translate3d(${L.clientX-h}px, ${L.clientY-_}px, 0)`)},E=L=>{if(l)return;if(!o){if(Math.hypot(L.clientX-i,L.clientY-a)<4)return;x(L)}k(L);const O=this.hitTest(L.clientX,L.clientY);O!==d&&(d?.onLeave?.(v),d=O,d?.onEnter?.(v))},q=L=>{if(l)return;if(!o){l=!0,b(),e.onClickOnly?.();return}const O=d;if(l=!0,b(),O&&O.accept(t)){e.onCommit?.(O),O.onDrop(v,{clientX:L.clientX,clientY:L.clientY});return}e.onCancel?.(O?"rejected":"no-target")},X=()=>v.cancel("pointercancel"),z=L=>{L.key==="Escape"&&v.cancel("escape")};return document.addEventListener("pointermove",E),document.addEventListener("pointerup",q),document.addEventListener("pointercancel",X),document.addEventListener("keydown",z),window.addEventListener("blur",X),this.active=v,v}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=yt(a.element);if(l>i){s=a,i=l;continue}l===i&&s&&wt(a.element,s.element)&&(s=a)}return s}}function yt(n){let e=0,t=n;for(;t;)e++,t=t.parentElement;return e}function wt(n,e){return(e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_FOLLOWING)!==0}function he(){return window.wp?.os??null}let ue=null;function K(){const n=he();return n?.dragManager?n.dragManager:(ue||(ue=new vt),ue)}function kt(n){const e=i=>{const a=i.detail?.payload;return a&&n.includes(a.type)?a.source:null},t=i=>e(i)?.classList.add("atf-is-dragging"),s=i=>e(i)?.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 Oe(n,e,t,s,i){const a=e.getBoundingClientRect();return i&&(i.style.width=`${Math.round(a.width)}px`,i.style.maxWidth=`${Math.round(a.width)}px`,i.style.boxSizing="border-box"),{type:n,source:e,data:t,ghost:{element:i,offsetX:s.clientX-a.left,offsetY:s.clientY-a.top,hint:{neutral:"",accept:"",reject:"",hidden:!0}}}}function qe(n,e,t,s){const i=Array.from(n.querySelectorAll(e)).filter(a=>a!==s&&!a.classList.contains("atf-drag-ghost"));for(let a=0;ai.fetch(l,c,{source:"allterrain-forms"}):(l,c)=>fetch(l,c))(t,{credentials:"same-origin",...e,headers:s});if(!o.ok){let l=`Request failed with status ${o.status}.`,c="";try{const d=await o.json();l=d.message??l,c=d.code??""}catch{}throw new ne(l,o.status,c)}if(o.status!==204)return await o.json()}const P=n=>pe(n);async function xt(n){if(!M?.wpRestUrl)throw new ne("AllTerrain Forms is not configured on this page.",0);const e=M.nonce?{"X-WP-Nonce":M.nonce}:{},t=he(),s=Me(M.wpRestUrl,n),i=t?.fetch?await t.fetch(s,{credentials:"same-origin",headers:e},{source:"allterrain-forms"}):await fetch(s,{credentials:"same-origin",headers:e});if(!i.ok)throw new ne(`Request failed with status ${i.status}.`,i.status);return await i.json()}function _t(n){if(!n||!n.includes("&"))return n;const e=document.createElement("textarea");return e.innerHTML=n,e.value}const G=(n,e)=>pe(n,{method:"POST",body:JSON.stringify(e)}),ie=n=>pe(n,{method:"DELETE"});function Ie(n){const e=new URLSearchParams;for(const[s,i]of Object.entries(n))i===void 0||i===""||i===!1||e.set(s,String(i));const t=e.toString();return t?`?${t}`:""}function ae(n){const e=n?.schema?.settings;return e&&(!e.themeOverrides||Array.isArray(e.themeOverrides))&&(e.themeOverrides={...e.themeOverrides}),n}const $={config:()=>P("/config"),listForms:()=>P("/forms"),getForm:n=>P(`/forms/${n}`).then(ae),createForm:n=>G("/forms",n).then(ae),updateForm:(n,e)=>G(`/forms/${n}`,e).then(ae),duplicateForm:n=>G(`/forms/${n}/duplicate`,{}).then(ae),deleteForm:n=>ie(`/forms/${n}`),preview:(n,e)=>G(`/forms/${n}/preview`,e),pages:()=>xt("wp/v2/pages?per_page=100&status=publish&orderby=title&order=asc&_fields=id,title").then(n=>n.map(e=>({id:e.id,title:_t(e.title?.rendered??"")||`#${e.id}`}))),mergeTags:n=>P(`/forms/${n}/merge-tags`).then(e=>e.groups),analytics:(n,e="")=>P(`/forms/${n}/analytics${e?`?dimension=${encodeURIComponent(e)}`:""}`),demoStatus:()=>P("/demo"),demoSeed:n=>G("/demo",n?{count:n}:{}),demoRemove:()=>ie("/demo"),listEntries:n=>P(`/entries${Ie(n)}`),getEntry:n=>P(`/entries/${n}`),updateEntry:(n,e)=>G(`/entries/${n}`,e),deleteEntry:n=>ie(`/entries/${n}`),exportEntries:n=>P(`/entries/export${Ie(n)}`),listThemes:()=>P("/themes"),saveTheme:n=>G("/themes",n),deleteTheme:n=>ie(`/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,i]of Object.entries(e.attrs??{}))i===void 0||i===!1||t.setAttribute(s,i===!0?"":String(i));Object.assign(t.style,e.style??{});for(const[s,i]of Object.entries(e.on??{}))t.addEventListener(s,i);for(const s of e.children??[])s==null||s===!1||t.append(s);return t}function F(n){n.replaceChildren()}function I(n){return r("span",{class:`dashicons ${n.startsWith("dashicons-")?n:`dashicons-${n}`}`,attrs:{"aria-hidden":"true"}})}function m(n,e,t){const s=e.id||`atf-c-${Math.random().toString(36).slice(2,9)}`;return e.id=s,r("div",{class:"atfb-row",children:[r("label",{class:"atfb-row__label",text:n,attrs:{for:s}}),e,t?r("p",{class:"atfb-row__hint",text:t}):null]})}function w(n,e,t=""){return r("input",{class:"atfb-input",type:"text",value:n,placeholder:t,on:{input:s=>e(s.target.value)}})}function R(n,e){if(Q("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 re(n,e,t=4){const s=r("textarea",{class:"atfb-input atfb-input--area",attrs:{rows:t},on:{input:i=>e(i.target.value)}});return s.value=n,s}function D(n,e,t){if(Q("os-select")&&Q("os-option")){const s=document.createElement("os-select");s.setAttribute("value",n),s.classList.add("atfb-field");for(const i of e){const a=document.createElement("os-option");a.setAttribute("value",i.value),a.textContent=i.label,s.append(a)}return s.addEventListener("os-pick",i=>{t(String(i.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 A(n,e,t){if(Q("os-checkbox-label")){const i=document.createElement("os-checkbox-label");return i.setAttribute("label",n),i.classList.add("atfb-check"),e&&i.setAttribute("checked",""),i.addEventListener("os-checkbox-change",a=>{t(!!a.detail?.checked)}),i}const s=r("input",{type:"checkbox",on:{change:i=>t(i.target.checked)}});return s.checked=e,r("label",{class:"atfb-check",children:[s,r("span",{text:n})]})}function S(n,e,t="secondary",s){const i=[s?I(s):null,r("span",{text:n})];if(Q("os-button")){const a=document.createElement("os-button");a.setAttribute("variant",t),a.setAttribute("type","button"),a.classList.add("atfb-button",`atfb-button--${t}`),a.addEventListener("click",e);for(const o of i)o&&a.append(o);return a}return r("button",{class:`atfb-button atfb-button--${t}`,type:"button",on:{click:e},children:i})}function Q(n){return typeof customElements<"u"&&!!customElements.get(n)}const St=["os-button","os-checkbox-label","os-number-field","os-select","os-option","os-segmented","os-segment","os-color-field","os-empty-state"];let oe=null;function Et(){if(oe)return oe;const n=window.wp?.os;return oe=n?.loadComponents?n.loadComponents(St).catch(()=>{}):Promise.resolve(),oe}async function fe(n,e=""){const t=window.wp?.os;return t?.confirm?t.confirm({title:e,message:n,danger:!0}):window.confirm(n)}function N(n,e="",t="info"){const s=window.wp?.os;if(s?.notify){s.notify({title:n,body:e,type:t});return}console.info(`[AllTerrain Forms] ${n}${e?`: ${e}`:""}`)}function Lt(n){let e=0,t;return(...s)=>{t=s,!e&&(e=window.requestAnimationFrame(()=>{e=0,n(...t)}))}}function me(n,e){let t=0;return(...s)=>{window.clearTimeout(t),t=window.setTimeout(()=>n(...s),e)}}function Ct(n){try{return window.localStorage.getItem(n)??""}catch{return""}}function Tt(n,e){try{window.localStorage.setItem(n,e)}catch{}}function Ne(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 Pe(){const n=[window];try{window.parent&&window.parent!==window&&n.push(window.parent)}catch{}return n}function $t(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 At=4e3,Dt=100;function Ft(n,e){const t=Date.now()+At,s=()=>{const i=$t(n);if(i&&i.id!==e){typeof i.close=="function"?i.close():n.wp?.os?.windowManager?.remove?.(i.id);return}!i&&Date.now()0?{form:t}:void 0;t>0&&It(t);for(const i of Pe())if(i.wp?.os?.openWindow?.(e,{source:"handoff",params:s})){window.setTimeout(()=>Ft(i,e),0);return}}function qt(){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 Pe())if(s.wp?.os?.openWindow?.(t,{source:"handoff"}))return})}const Mt="allterrain-forms/requested-form";function It(n){try{window.sessionStorage.setItem(Mt,String(n))}catch{}}const Re={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"},Be=["empty","not_empty"];function He(n,e){const t=n.find(s=>s.id===e);return t?t.label||"an untitled question":null}function je(n,e,t){return n.find(a=>a.id===e)?.choices?.find(a=>a.value===t)?.label||t}function Nt(n,e){const t=Re[n.operator]??String(n.operator);if(Be.includes(n.operator))return t;const s=je(e,n.field,n.value);return`${n.operator==="is"?"":`${t} `}${s!==""?s:"(nothing)"}`}function Ue(n,e){const t=He(e,n.field),s=Re[n.operator]??String(n.operator);if(t===null)return[{kind:"field",text:"a question that no longer exists",fieldId:n.field,missing:!0}];const i=[{kind:"field",text:t,fieldId:n.field,missing:!1},{kind:"operator",text:s}];if(Be.includes(n.operator))return i;const a=je(e,n.field,n.value);return i.push({kind:"value",text:a!==""?a:"(nothing)"}),i}function Pt(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((i,a)=>{a>0&&s.push({kind:"join",text:t.match==="all"?"and":"or"}),s.push(...Ue(i,e))}),s}function We(n){return n.map(e=>e.text).join(" ")}function Rt(n,e){return We(Ue(n,e))}function be(n){const e=[];for(const t of n){const s=t.logic;if(s?.enabled)for(const i of s.rules)!i.field||i.field===t.id||e.push({from:i.field,to:t.id,label:Rt(i,n),short:Nt(i,n),action:s.action,broken:He(n,i.field)===null})}return e}function Bt(n){const e=new Map,t=new Set;for(const s of be(n)){const i=`${s.from}->${s.to}`;s.broken||t.has(i)||(t.add(i),e.set(s.from,(e.get(s.from)??0)+1))}return e}const Ye=10,ze=22,J="http://www.w3.org/2000/svg";class Ht{constructor(e){this.edges=[],this.teardowns=[],this.frame=0,this.host=e,this.svg=document.createElementNS(J,"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 i=new ResizeObserver(t);i.observe(e),this.teardowns.push(()=>i.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 i=this.cardRect(s.from),a=this.cardRect(s.to);if(!i||!a)continue;const o=t.get(s.from)??0;t.set(s.from,o+1),this.drawEdge(s,i,a,e,o)}}cardRect(e){const t=this.host.querySelector(`[data-atfb-card="${CSS.escape(e)}"]`);return t?t.getBoundingClientRect():null}drawEdge(e,t,s,i,a){const o=t.right-i.left,l=t.top+t.height/2-i.top,c=s.right-i.left,d=s.top+s.height/2-i.top,h=Math.max(0,i.width-Ye-Math.max(o,c)),_=22+a*11+Math.min(22,Math.abs(d-l)/8),b=Math.max(12,Math.min(_,h*.4)),v=document.createElementNS(J,"path");v.setAttribute("d",`M ${o} ${l} C ${o+b} ${l}, ${c+b} ${d}, ${c} ${d}`),v.setAttribute("class",`atfb-logicmap__path is-${e.action}${e.broken?" is-broken":""}`),v.dataset.from=e.from,v.dataset.to=e.to;const x=document.createElementNS(J,"title");x.textContent=e.label,v.append(x);const k=document.createElementNS(J,"circle");k.setAttribute("cx",String(c)),k.setAttribute("cy",String(d)),k.setAttribute("r","3.5"),k.setAttribute("class",`atfb-logicmap__dot is-${e.action}${e.broken?" is-broken":""}`),k.dataset.from=e.from,k.dataset.to=e.to,this.svg.append(v,k);const E=document.createElementNS(J,"text");E.setAttribute("x",String(i.width-Ye)),E.setAttribute("y",String(d+3)),E.setAttribute("text-anchor","end"),E.setAttribute("class","atfb-logicmap__label"),E.dataset.from=e.from,E.dataset.to=e.to,E.textContent=e.short.length>ze?`${e.short.slice(0,ze-1)}…`:e.short;const q=document.createElementNS(J,"title");q.textContent=e.label,E.append(q),this.svg.append(E)}}const jt={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 Ut(n){return jt[n]??"text"}function Ge(n,e){const t=/^choice:(\d+):(label|value)$/.exec(e);return String(t?n.choices?.[Number(t[1])]?.[t[2]]??"":n[e]??"")}function W(n){const{value:e,onInput:t,onCommit:s}=n,i=r("span",{class:`${n.class} atfb-editable`,text:e,attrs:{contenteditable:"plaintext-only",role:"textbox",spellcheck:"false","data-placeholder":n.placeholder}});return n.bind&&(i.dataset.atfbBind=n.bind),i.addEventListener("pointerdown",a=>a.stopPropagation()),i.addEventListener("input",()=>t(i.textContent??"")),i.addEventListener("keydown",a=>{a.key==="Enter"&&(a.preventDefault(),i.blur()),a.key==="Escape"&&(i.textContent=e,i.blur()),a.stopPropagation()}),i.addEventListener("blur",()=>s?.()),i}function Wt(n){const e=r("input",{class:"atf-choice__input",type:n==="checkboxes"||n==="multiselect"?"checkbox":"radio"});return e.disabled=!0,e}function Yt(n,e,t){const s=Ut(n.type),i=W({value:n.label,placeholder:"Write the question…",class:"atf-label",bind:"label",onInput:o=>t.edit(l=>{l.label=o}),onCommit:()=>t.restructure(()=>{})}),a=[s==="static"||s==="toggle"?null:i,Gt(n,e,s,t),zt(n,e,t)];return r("div",{class:"atfb-preview atf-form",children:[r("div",{class:`atf-field atf-field--${n.type}`,children:a})]})}function zt(n,e,t){if(!e?.supports.includes("hint"))return null;const s=W({value:n.hint??"",placeholder:"Add a hint…",class:"atf-hint",bind:"hint",onInput:i=>t.edit(a=>{a.hint=i})});return s.setAttribute("aria-label","Hint"),r("p",{class:"atfb-preview__hint",children:[s]})}function Gt(n,e,t,s){switch(t){case"text":return ge(n,"atf-input",s);case"textarea":return ge(n,"atf-input atf-textarea",s,!0);case"select":return r("div",{class:"atfb-preview__select",children:[ge(n,"atf-input atf-select",s)]});case"options":return Kt(n,s);case"toggle":return r("div",{class:n.type==="switch"?"atf-toggle atf-toggle--switch":"atf-toggle",children:[(()=>{const i=r("input",{class:"atf-toggle__input",type:"checkbox"});return i.disabled=!0,i})(),W({value:n.label||"",placeholder:n.type==="consent"?"What are they agreeing to?…":"What does this turn on?…",class:"atf-toggle__label",bind:"label",onInput:i=>s.edit(a=>{a.label=i}),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 Jt(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"?Xt(n,s):null]})}}function ge(n,e,t,s=!1){const i=W({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:a=>t.edit(o=>{o.placeholder=a})});return i.setAttribute("aria-label","Placeholder"),i}function ve(n,e,t,s,i,a){return r("span",{class:`atf-button ${t} atfb-preview__button`,children:[W({value:n,placeholder:e,class:"atfb-preview__button-text",bind:s,onInput:o=>i.edit(l=>a(l,o))})]})}function Xt(n,e){return ve(String(n.addLabel??""),"Add another","atf-button--secondary","addLabel",e,(t,s)=>{t.addLabel=s})}function Kt(n,e){const t=r("div",{class:"atf-choices__list"});return(n.choices??[]).forEach((s,i)=>{t.append(r("div",{class:"atf-choice atfb-preview__option",children:[Wt(n.type),W({value:s.label,placeholder:"Option…",class:"atf-choice__label",bind:`choice:${i}:label`,onInput:a=>{e.edit(o=>{const l=o.choices?.[i];if(!l)return;const c=!l.value||l.value===l.label;l.label=a,c&&(l.value=a)})}}),r("button",{class:"atfb-preview__remove",type:"button",title:"Remove this option",attrs:{"aria-label":`Remove ${s.label||"this option"}`},on:{pointerdown:a=>a.stopPropagation(),click:a=>{a.stopPropagation(),e.restructure(o=>{o.choices.splice(i,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(i=>{const a=i.choices.length+1;i.choices.push({label:`Option ${a}`,value:`Option ${a}`})})}},children:[r("span",{text:"+ Add option"})]})),r("fieldset",{class:"atf-choices",children:[t]})}function Jt(n,e,t){return n.type==="heading"?W({value:n.label,placeholder:"Section heading…",class:"atf-heading",bind:"label",onInput:s=>t.edit(i=>{i.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:[W({value:n.label,placeholder:"Name this step…",class:"atf-progress__label",bind:"label",onInput:s=>t.edit(i=>{i.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:[ve(String(n.prevLabel??""),"Back","atf-button--secondary","prevLabel",t,(s,i)=>{s.prevLabel=i}),ve(String(n.nextLabel??""),"Next","","nextLabel",t,(s,i)=>{s.nextLabel=i})]})]}):r("p",{class:"atfb-preview__summary",text:`${e?.label??n.type} — nothing is shown to the visitor here.`})}const ye=new Map;function we(n){let e=ye.get(n);return e||(e=$.mergeTags(n).catch(()=>[]),ye.set(n,e)),e}function Vt(n){ye.delete(n)}function Qt(n){const e=new Map;for(const t of n)for(const s of t.items)e.set(s.tag,s);return e}function Zt(n,e){const t=Qt(e);return n.replace(/\{[a-z_]+(?::[^}]*)?\}/gi,s=>{const i=t.get(s.toLowerCase());return i?i.sample:s})}function es(n){return/\{[a-z_]+(?::[^}]*)?\}/i.test(n)}let Y=null;function Z(){Y?.remove(),Y=null}typeof document<"u"&&(document.addEventListener("pointerdown",n=>{const e=n.target;e?.closest(".atfb-tagpick__open")||Y&&!Y.contains(e)&&Z()}),document.addEventListener("keydown",n=>{n.key==="Escape"&&Y&&(Z(),n.stopPropagation())}));function Xe(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 i=t+e.length;n.setSelectionRange(i,i),n.focus(),n.dispatchEvent(new Event("input",{bubbles:!0}))}function ts(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 ss(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"}),i=a=>{s.replaceChildren();const o=a.trim().toLowerCase();let l=0;for(const c of n){const d=c.items.filter(h=>!o||h.label.toLowerCase().includes(o)||h.tag.toLowerCase().includes(o));if(!d.length){c.empty&&!o&&!c.items.length&&s.append(r("p",{class:"atfb-tagpick__group",text:c.label}),r("p",{class:"atfb-tagpick__empty",text:c.empty}));continue}s.append(r("p",{class:"atfb-tagpick__group",text:c.label}));for(const h of d)l+=1,s.append(r("button",{class:"atfb-tagpick__item",type:"button",on:{click:()=>{e(h.tag),Z()}},children:[r("span",{class:"atfb-tagpick__item-main",children:[r("span",{class:"atfb-tagpick__label",text:h.label}),r("code",{class:"atfb-tagpick__tag",text:h.tag})]}),h.hint||h.sample?r("span",{class:"atfb-tagpick__meta",text:h.sample?`e.g. ${h.sample}`:h.hint}):null]}))}!l&&o&&s.append(r("p",{class:"atfb-tagpick__empty",text:`Nothing matches “${a}”.`}))};return i(""),t.addEventListener("input",()=>i(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 Ke(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:[I("shortcode"),r("span",{text:"Insert a value"})]}),s=r("div",{class:"atfb-taggable",children:[n,r("div",{class:"atfb-taggable__tools",children:[t]})]}),i=e.preview===!1?null:r("p",{class:"atfb-taggable__preview"});i&&s.append(i);const a=()=>{if(i){if(!es(n.value)){i.textContent="",i.hidden=!0;return}we(e.formId).then(o=>{i.hidden=!1,i.replaceChildren(r("span",{class:"atfb-taggable__preview-label",text:"Reads as"}),r("span",{text:Zt(n.value,o)}))})}};return n.addEventListener("input",a),a(),t.addEventListener("click",o=>{if(o.stopPropagation(),Y&&s.contains(Y)){Z();return}Z(),we(e.formId).then(l=>{const c=ss(l,d=>{Xe(n,d),a()});s.append(c),Y=c,(window.innerHeight-c.getBoundingClientRect().topts(s))&&c.classList.add("atfb-tagpick--above"),c.querySelector(".atfb-tagpick__search")?.focus()})}),s}function Je(n,e){const t=parseFloat(String(n??""));return Number.isFinite(t)?t:e}function Ve(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:ke.map(({value:n,label:e})=>({value:n,label:e})),owns:["radius-field","radius-button","radius-card","radius-check"],read:n=>Ve(Je(n["radius-field"],4),ke),apply:n=>{const e=ke.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:xe.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=>Ve(Je(n["pad-field-y"],9),xe),apply:n=>{const e=xe.find(t=>t.value===n)?.at??9;return{"pad-field-y":`${e}px`,"pad-field-x":`${Math.round(e*1.4)}px`,"gap-fields":`${Math.round(e*2.2)}px`,"gap-label":`${Math.max(4,Math.round(e*.7))}px`,"button-pad-y":`${e+2}px`,"button-pad-x":`${Math.round(e*2.2)}px`}}},{id:"shadow",label:"Depth",hint:"How far the form sits off the page.",kind:"scale",steps:ns,owns:["shadow-field","shadow-field-focus","shadow-button","shadow-button-hover","shadow-card"],read:n=>{const e=n["shadow-card"]??"none";return e==="none"||e.trim()===""?n["shadow-field"]&&n["shadow-field"]!=="none"?"hard":"none":e.includes("0 1px")||e.includes("2px")?"subtle":"lifted"},apply:n=>{switch(n){case"subtle":return{"shadow-field":"none","shadow-field-focus":"none","shadow-button":"0 1px 2px rgba( 0, 0, 0, 0.12 )","shadow-button-hover":"0 2px 4px rgba( 0, 0, 0, 0.16 )","shadow-card":"0 1px 3px rgba( 0, 0, 0, 0.1 )"};case"lifted":return{"shadow-field":"none","shadow-field-focus":"0 4px 12px rgba( 0, 0, 0, 0.12 )","shadow-button":"0 4px 10px rgba( 0, 0, 0, 0.16 )","shadow-button-hover":"0 8px 20px rgba( 0, 0, 0, 0.2 )","shadow-card":"0 10px 30px rgba( 0, 0, 0, 0.14 )"};case"hard":return{"shadow-field":"3px 3px 0 currentColor","shadow-field-focus":"5px 5px 0 currentColor","shadow-button":"4px 4px 0 currentColor","shadow-button-hover":"2px 2px 0 currentColor","shadow-card":"none"};default:return{"shadow-field":"none","shadow-field-focus":"none","shadow-button":"none","shadow-button-hover":"none","shadow-card":"none"}}}},{id:"field-style",label:"Fields",hint:"How an input is drawn.",kind:"choice",steps:is,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:as,owns:["label-position"],read:n=>n["label-position"]??"top",apply:n=>({"label-position":n})}]}function rs(n){return _e().find(e=>e.owns.includes(n))??null}function Qe(n){let e=n.activeSlug,t={...n.overrides},s=n.themes;const i=u=>{t=u,n.onOverridesReplaced?.({...u})},a=r("div",{class:"atfs-preview__frame"}),o=r("div",{class:"atfs-controls__body"}),l=r("div",{class:"atfs-quick"}),c=r("div",{class:"atfs-themes"}),d=me(async()=>{try{const u=await n.previewFor(e,t),p=a.closest(".atf-studio__preview"),f=p?.scrollTop??0;a.innerHTML=u,p&&(p.scrollTop=f),document.dispatchEvent(new CustomEvent("atf-refresh"))}catch(u){F(a),a.append(r("p",{class:"atfb-error",text:u instanceof Error?u.message:"Preview failed."}))}},180),h={"label-position":"atf-labels-","field-style":"atf-fields-"},_=(u,p,f)=>{const g=f.replace(/[^a-z0-9_-]/gi,"");for(const y of[...u.classList])y.startsWith(p)&&u.classList.remove(y);g&&u.classList.add(p+g)},b=u=>{const p=a.querySelector(".atf-form-wrap"),f=p?.querySelector(".atf-form");if(p)for(const[g,y]of Object.entries(u))y===""?p.style.removeProperty(`--atf-${g}`):p.style.setProperty(`--atf-${g}`,y),f&&h[g]&&_(f,h[g],y||(s.find(T=>T.slug===e)?.resolved[g]??""))},v=u=>{b(u.resolved);const p=a.querySelector(".atf-form");p&&(_(p,"atf-theme-",u.slug),p.classList.toggle("atf-is-dark",!!u.dark))},x=u=>t[u.token]!==void 0?t[u.token]:s.find(f=>f.slug===e)?.resolved?.[u.token]??u.default,k=()=>{F(c);for(const u of s){const p=r("button",{class:`atfs-theme${u.slug===e?" is-active":""}`,type:"button",attrs:{"aria-pressed":u.slug===e,title:u.description},children:[r("span",{class:"atfs-theme__chip",style:{background:u.resolved.surface??"#fff",borderColor:u.resolved.border??"#ccc",borderRadius:u.resolved["radius-field"]??"4px",boxShadow:u.resolved["shadow-card"]??"none"},children:[r("span",{class:"atfs-theme__accent",style:{background:u.resolved.accent??"#2271b1",borderRadius:u.resolved["radius-button"]??"4px"}})]}),r("span",{class:"atfs-theme__name",text:u.label}),u.custom?r("span",{class:"atfb-badge",text:"yours"}):null],on:{click:()=>{e=u.slug,i({}),n.onTheme(e),v(u),k(),z(),X(),mt()}}});c.append(p)}},E=()=>{const p={...s.find(f=>f.slug===e)?.resolved??{}};for(const[f,g]of Object.entries(t))p[f]=g;return p},q=u=>{const p=l.closest(".atf-studio__controls"),f=p?.scrollTop??0;u(),p&&(p.scrollTop=f)},X=me(()=>q(V),150),z=()=>{const u=E(),p=l.querySelectorAll(".atfs-dial");_e().forEach((f,g)=>{const y=p[g];if(!y)return;const T=f.read(u);if(f.kind==="colour"){const C=y.querySelector('input[type="color"]'),B=y.querySelector("input.atfb-input");C&&document.activeElement!==C&&(C.value=le(T)),B&&document.activeElement!==B&&(B.value=T);return}y.querySelectorAll(".atfs-segment").forEach((C,B)=>{const j=f.steps?.[B]?.value===T;C.classList.toggle("is-on",j),C.setAttribute("aria-pressed",String(j))})})},L=(u,p)=>{const f=u.apply(p,E());for(const[g,y]of Object.entries(f))t[g]=y,n.onOverride(g,y);b(f),z(),X()},O=()=>{F(l);const u=E();for(const p of _e()){const f=p.read(u);let g;if(p.kind==="colour"){const y=r("input",{class:"atfs-color",type:"color",value:le(f),attrs:{"aria-label":p.label},on:{input:T=>L(p,T.target.value)}});g=r("div",{class:"atfs-color-row",children:[y,w(f,T=>L(p,T))]})}else g=r("div",{class:`atfs-segmented atfs-segmented--${p.kind}`,attrs:{role:"group","aria-label":p.label},children:(p.steps??[]).map(y=>r("button",{class:`atfs-segment${y.value===f?" is-on":""}`,type:"button",text:y.label,attrs:{"aria-pressed":y.value===f},on:{click:()=>L(p,y.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})]}),g]}))}},V=()=>{F(o);const u=new Map;for(const f of n.tokens){const g=u.get(f.group)??[];g.push(f),u.set(f.group,g)}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[f,g]of u)o.append(r("details",{class:"atfs-group",attrs:{open:f==="colour"},children:[r("summary",{text:p[f]??f}),...g.map(y=>As(y))]}))},As=u=>{const p=x(u),f=C=>{C===""?delete t[u.token]:t[u.token]=C,n.onOverride(u.token,C),b({[u.token]:C})};let g;if(u.control==="color"){const C=r("input",{class:"atfs-color",type:"color",value:le(p),attrs:{"aria-label":`${u.label} colour`},on:{input:j=>{const se=j.target.value;B.value=se,f(se)}}}),B=w(p,j=>{C.value=le(j),f(j)});g=r("div",{class:"atfs-color-row",children:[C,B]})}else if(u.control==="select")g=D(p,(u.options??[]).map(C=>({value:C,label:C})),f);else if(u.control==="length"){const C=parseFloat(p)||0,B=u.unit??"px",j=u.token.includes("radius")?60:80,se=r("input",{class:"atfs-range",type:"range",value:String(C),attrs:{min:"0",max:String(j),step:"1","aria-label":u.label},on:{input:de=>{const gt=`${de.target.value}${B}`;bt.value=gt,f(gt)}}}),bt=w(p,de=>{se.value=String(parseFloat(de)||0),f(de)});g=r("div",{class:"atfs-length-row",children:[se,bt]})}else g=w(p,f);const y=m(u.label,g),T=rs(u.token);return T&&(y.classList.add("is-dialled"),y.append(r("span",{class:"atfs-owned",text:T.label}))),t[u.token]!==void 0&&(y.classList.add("is-overridden"),y.append(r("button",{class:"atfs-reset",type:"button",text:"Reset",attrs:{"aria-label":`Reset ${u.label} to the theme's value`},on:{click:()=>{f(""),q(V)}}}))),y},Ds=async()=>{const u=s.find(g=>g.slug===e),p=u?`${u.label} (mine)`:"My theme",f=window.prompt("Name this theme",p);if(f)try{const g={};for(const T of n.tokens){const C=x(T);C!==T.default&&(g[T.token]=C)}const y=await $.saveTheme({label:f,tokens:g});s=[...s.filter(T=>T.slug!==y.slug),y],n.onThemesChanged(s),e=y.slug,i({}),n.onTheme(e),k(),z(),q(V),N("Theme saved",y.label)}catch(g){N("Could not save the theme",g instanceof Error?g.message:"","error")}},Fs=async()=>{const u=s.find(p=>p.slug===e);if(u?.custom&&await fe(`Delete “${u.label}”? Forms using it fall back to Clean.`,"Delete theme"))try{await $.deleteTheme(u.id),s=s.filter(f=>f.slug!==u.slug),n.onThemesChanged(s),e="clean",i({}),n.onTheme(e),k(),z(),q(V);const p=s.find(f=>f.slug===e);p&&v(p)}catch(p){N("Could not delete the theme",p instanceof Error?p.message:"","error")}},Os=async()=>{const u=s.find(g=>g.slug===e),p={label:u?.label??e,tokens:{...u?.tokens??{},...t}},f=JSON.stringify(p,null," ");try{await navigator.clipboard.writeText(f),N("Theme copied","Paste it into another site to import it.")}catch{window.prompt("Copy this theme",f)}},qs=()=>{const u=window.prompt("Paste a theme");if(u)try{const p=JSON.parse(u);if(!p.tokens||typeof p.tokens!="object")throw new Error("That does not look like a theme.");for(const[f,g]of Object.entries(p.tokens))t[f]=String(g),n.onOverride(f,String(g));b({...t}),z(),q(V)}catch(p){N("Could not read that theme",p instanceof Error?p.message:"","error")}},ft=S("Delete",()=>void Fs(),"danger"),mt=()=>{const u=s.find(p=>p.slug===e);ft.hidden=!u?.custom};return k(),O(),V(),d(),mt(),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:[S("Save as a theme",()=>void Ds(),"primary"),S("Export",()=>void Os()),S("Import",qs),ft]})]}),c]}),r("div",{class:"atf-studio__panes",children:[r("aside",{class:"atf-studio__controls",children:[r("p",{class:"atfb-hint",text:"Changes apply to this form only, until you save them as a theme."}),l,r("details",{class:"atfs-advanced",children:[r("summary",{text:"Every setting"}),r("p",{class:"atfb-hint",text:"The full token list. The dials above write into it."}),o]})]}),r("main",{class:"atf-studio__preview",children:[r("h2",{class:"screen-reader-text",text:"Live preview"}),a]})]})]})}async function Se(){const n=document.querySelector("[data-atfs-root]");if(!n||n.dataset.atfsMounted)return;n.dataset.atfsMounted="1",Ne(n);const e=n.querySelector("[data-atfs-bar]"),t=n.querySelector(".atfs__body")??n;try{const[s,i,a]=await Promise.all([$.config(),$.listThemes(),$.listForms()]);if(!a.length){F(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=a[0],l={},c=o.theme;const d=()=>{F(t),t.append(Qe({themes:i,tokens:s.tokens,activeSlug:c,overrides:l,standalone:!0,onTheme:h=>{c=h},onOverride:(h,_)=>{_===""?delete l[h]:l[h]=_},onOverridesReplaced:h=>{l={...h}},previewFor:async(h,_)=>{const b=await $.getForm(o.id);b.schema.settings.theme=h,b.schema.settings.themeOverrides=_;const{html:v}=await $.preview(o.id,{schema:b.schema,theme:h});return v},onThemesChanged:h=>{i.length=0,i.push(...h)}}))};e&&(F(e),e.append(r("span",{class:"atfs__label",text:"Preview against"}),D(String(o.id),a.map(h=>({value:String(h.id),label:h.title||"(untitled)"})),h=>{o=a.find(_=>String(_.id)===h)??a[0],l={},d()}))),d()}catch(s){F(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 Se()):Se(),document.addEventListener("os-window-content-loaded",()=>void Se()));function le(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 os={min:-1,max:-1,sum:-1,avg:-1,round:-1,ceil:1,floor:1,abs:1,sqrt:1,pow:2},Ze={"+":{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 ls(n,e,t=[]){if(!n||!n.trim()||n.length>2e3)return null;const s=cs(n,e,t),i=ds(s);if(!i)return null;const a=us(i);if(!a)return null;const o=ps(a);return o===null||!Number.isFinite(o)?null:o}function cs(n,e,t){return n.replace(/\{([a-zA-Z0-9_]+)\}/g,(s,i)=>{const a=Object.prototype.hasOwnProperty.call(e,i)?e[i]:null,o=t.find(c=>c.id===i)??null,l=et(a,o).toFixed(10).replace(/0+$/,"").replace(/\.$/,"");return l===""||l==="-"?"0":l})}function et(n,e){if(typeof n=="boolean")return n?1:0;if(Array.isArray(n))return n.reduce((i,a)=>i+et(a,e),0);if(n==null||n==="")return 0;const t=e?.choices??[];for(const i of t)if(String(i.value)===String(n)){if(typeof i.price=="number")return i.price;if(typeof i.points=="number")return i.points;break}const s=Number(n);return Number.isFinite(s)?s:0}function ds(n){const e=[];let t=0;for(;ta.precedence||l.precedence===a.precedence&&!a.right){e.push(t.pop());continue}break}t.push(i);break}case"(":t.push(i);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 a=t.pop();a.arity=s.length?s.pop():1,e.push(a)}break}}for(;t.length;){const i=t.pop();if(i.type==="(")return null;e.push(i)}return e}function ps(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(),i=e.pop();switch(t.value){case"+":e.push(i+s);break;case"-":e.push(i-s);break;case"*":e.push(i*s);break;case"/":e.push(s===0?0:i/s);break;case"%":e.push(s===0?0:i%s);break;case"^":e.push(Math.pow(i,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 ms=["sum","min","max","avg","round","ceil","floor","abs","sqrt","pow"],bs=["number","range","scale","rating","total","select","multiselect","radio","checkboxes","switch","quiz"];function Ee(n,e){return n.filter(t=>t.id!==e&&bs.includes(t.type))}function gs(n,e){const t={};return Ee(n,e).forEach((s,i)=>{t[s.id]=i+1}),t}function vs(n){const e=r("div",{class:"atfb-overlay"}),t=()=>{e.remove(),document.removeEventListener("keydown",s)},s=b=>{b.key==="Escape"&&t()},i=r("textarea",{class:"atfb-input atfb-formula__input",attrs:{rows:"3","aria-label":"Formula"}});i.value=String(n.field.formula??"");const a=r("p",{class:"atfb-formula__result",attrs:{"aria-live":"polite"}}),o=gs(n.fields,n.field.id),l=()=>{const b=i.value.trim();if(b===""){a.textContent="Empty. Reference a question below to start.",a.classList.remove("is-error");return}const v=ls(b,o,n.fields);if(v===null){a.textContent="This does not compute yet — check the braces and parentheses.",a.classList.add("is-error");return}const x=Ee(n.fields,n.field.id).map((k,E)=>`${k.label||k.id} = ${E+1}`).join(", ");a.textContent=`With sample answers (${x}): ${v}`,a.classList.remove("is-error")};i.addEventListener("input",l);const c=(b,v,x=0)=>r("button",{class:"atfb-formula__chip",type:"button",text:b,on:{click:()=>{if(Xe(i,v),x>0){const k=(i.selectionStart??i.value.length)-x;i.setSelectionRange(k,k)}}}}),d=Ee(n.fields,n.field.id),h=r("div",{class:"atfb-formula__chips",children:d.length?d.map(b=>c(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."})]}),_=r("div",{class:"atfb-formula__chips",children:ms.map(b=>c(`${b}()`,`${b}()`,1))});e.append(r("div",{class:"atfb-modal atfb-formula",attrs:{role:"dialog","aria-label":"Formula editor"},children:[r("h2",{text:"Formula"}),i,a,m("Your questions",h,"Click one to reference its answer."),m("Functions",_),r("div",{class:"atfb-modal__actions",children:[S("Cancel",t),S("Save formula",()=>{n.onSave(i.value.trim()),t()},"primary")]})]})),e.addEventListener("click",b=>{b.target===e&&t()}),document.addEventListener("keydown",s),n.root.append(e),l(),i.focus(),i.setSelectionRange(i.value.length,i.value.length)}function Le(){return window.wp?.os??null}const tt="allterrain-forms/preview",Ce="allterrain-forms-preview";function ys(n){const e=Le();if(!e?.registerTitleBarButton)return()=>{};const t=()=>{try{e.registerTitleBarButton({id:tt,label:"Preview this form",icon:"dashicons-visibility",placement:"right",order:90,match:s=>{const i=s?.id??s?.config?.id??"";return i==="allterrain-forms"||i.startsWith("allterrain-forms#")},onClick:()=>void st(n),owner:"allterrain-forms-builder"})}catch{}};return e.ready?e.ready(t):t(),()=>{try{e.unregisterTitleBarButton?.(tt)}catch{}}}async function st(n){n.isDirty()&&await n.save();const e=n.current();e&&nt(e.id,e.title,e.previewUrl)}function nt(n,e,t){const s=Le();if(!s?.windowManager?.open){window.open(t,"_blank","noopener");return}s.windowManager.open({id:`${Ce}-${n}`,baseId:Ce,url:t,title:`Preview: ${e}`,icon:"dashicons-visibility"})}function ws(n,e,t){if(!Le()?.windowManager?.open||!document.querySelector(`[data-window-id="${Ce}-${n}"]`))return;const a=t.includes("?")?"&":"?";nt(n,e,`${t}${a}atf_r=${Date.now()}`)}const ks="allterrain-forms/form";function xs(){return window.wp?.os?.relations??null}function _s(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 Ss=6e3,it=120;function at(n,e){const t=xs();if(Te.set(n,e),!t?.set)return;const s=a=>{if(ce.get(n)!==i)return;const o=_s(n);if(!o){Date.now()s(a),it);return}try{t.set(o,e)}catch(c){rt||(rt=!0,console.error("[AllTerrain Forms] The shell refused a window identity.",c,e)),ce.delete(n);return}if(!e||t.get?.(o)?.id===e.id||Date.now()>=a){ce.delete(n);return}window.setTimeout(()=>s(a),it)},i=Symbol("atf-identity");ce.set(n,i),s(Date.now()+Ss)}let rt=!1;const ce=new WeakMap,Te=new Map;function Es(){for(const[n,e]of Te){if(!n.isConnected){Te.delete(n);continue}at(n,e)}}if(typeof document<"u")for(const n of["os-window-content-loaded","os-window-opened"])document.addEventListener(n,()=>Es());function Ls(n,e){return{type:ks,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 ee="allterrain-forms/field",Cs=["openstation/file","desktop-mode/file","openstation/attachment"],ot="allterrain-forms/logic-map-v2";function H(n,e){return n.dataset.atfbBind=e,n}const lt={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"}}},Ts={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 ct(n,e,t){const s=n[e.key],i=a=>t(e.key,a);if(e.control==="checkbox")return A(e.label,!!s,i);if(e.control==="commas"){const a=Array.isArray(s)?s:[];return m(e.label,w(a.join(", "),o=>{i(o.split(",").map(l=>l.trim().replace(/^\./,"").toLowerCase()).filter(Boolean))}),e.hint)}return e.control==="select"?m(e.label,D(String(s??""),e.options??[],i),e.hint):e.control==="textarea"?m(e.label,re(String(s??""),i),e.hint):e.control==="number"?m(e.label,R(String(s??""),i),e.hint):m(e.label,H(w(String(s??""),i),e.key),e.hint)}function dt(n,e){const t=new Set(n.map(i=>i.key).filter(Boolean));let s=n.length+1;return e.split(` -`).map(i=>i.trim()).filter(Boolean).map((i,a)=>{const o=n[a]?.key;if(o)return{key:o,label:i};for(;t.has(`r${s}`);)s+=1;return t.add(`r${s}`),{key:`r${s}`,label:i}})}function ht(n,e,t){const s=n.findIndex(a=>a.id===e);if(s<0)return null;const i=Math.max(0,Math.min(t,n.length-1));return i===s?null:{from:s,to:i}}const $e=[{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}"}],$s=["About the person filling it in","The date and time","About this site"],Ae=(n,e)=>M?.i18n?.[n]??e;class ut{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=Ct(ot)!=="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=me(()=>{this.save(!0)},2500),this.renderInspector=Lt(()=>{if(F(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(!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),i=s?.supports??[],a=(o,l)=>{const c=this.liveField(t.id);c&&(c[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}}`})),i.includes("label")&&this.inspector.append(m("Label",H(w(t.label,o=>a("label",o)),"label"))),i.includes("placeholder")&&this.inspector.append(m("Placeholder",H(w(t.placeholder,o=>a("placeholder",o)),"placeholder"))),i.includes("hint")&&this.inspector.append(m("Hint",H(w(t.hint,o=>a("hint",o)),"hint"),"Shown under the field, and read out with it.")),i.includes("nextlabel")&&this.inspector.append(m("Next button",H(w(String(t.nextLabel??""),o=>a("nextLabel",o)),"nextLabel"),"Leave empty for “Next”.")),i.includes("prevlabel")&&this.inspector.append(m("Back button",H(w(String(t.prevLabel??""),o=>a("prevLabel",o)),"prevLabel"),"Leave empty for “Back”.")),i.includes("addlabel")&&this.inspector.append(m("Add button",H(w(String(t.addLabel??""),o=>a("addLabel",o)),"addLabel"),"Leave empty for “Add another”.")),i.includes("required")&&this.inspector.append(A("Required",t.required,o=>a("required",o))),i.includes("width")&&this.inspector.append(m("Width",D(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=>a("width",o)))),s?.choices&&this.inspector.append(this.renderChoicesEditor(t,a)),this.renderTypeSettings(t,s,i,a),(t.type==="total"||i.includes("formula"))&&this.inspector.append(m("Formula",r("div",{class:"atfb-formula__row",children:[w(String(t.formula??""),o=>a("formula",o)),S("Formula editor",()=>vs({root:this.root,fields:this.schema?.fields??[],field:t,onSave:o=>{a("formula",o),this.renderInspector()}}))]}),"Reference answers with braces — {f1} * {f2} + 10 — or open the editor and click them in."),m("Currency symbol",w(String(t.currency??""),o=>a("currency",o)))),this.inspector.append(this.renderValidationSection(t,i,a)),this.inspector.append(this.renderLogicSection(t,a)),i.includes("prefill")&&this.inspector.append(this.prefillControl(t,a)),i.includes("css")&&this.inspector.append(m("CSS class",w(t.cssClass,o=>a("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")}async start(){try{const[s,i,a]=await Promise.all([$.config(),$.listThemes(),$.listForms()]);this.config=s,this.themes=i,this.forms=a}catch(s){this.fail(s);return}this.teardowns.push(kt([ee])),this.teardowns.push(ys({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))};this.root.addEventListener("keydown",t),this.teardowns.push(()=>this.root.removeEventListener("keydown",t)),this.renderBar(),this.renderPalette(),this.forms.length?await this.open(this.forms[0].id):this.renderFormsList()}destroy(){this.canvasTarget?.(),this.canvasTarget=null,this.teardowns.forEach(e=>e()),this.teardowns=[]}fail(e){F(this.bar),this.bar.append(r("p",{class:"atfb-error",text:e instanceof Error?e.message:"Something went wrong loading your forms."}))}renderBar(){F(this.bar);const e=D(String(this.form?.id??""),[...this.forms.map(i=>({value:String(i.id),label:i.title||"(untitled)"}))],i=>void this.open(Number(i)));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:i=>{this.form&&(this.form.title=i.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(([i,a])=>r("button",{class:`atfb-tab${this.tab===i?" is-active":""}`,type:"button",text:a,attrs:{role:"tab","aria-selected":this.tab===i},on:{click:()=>{this.tab=i,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),S("New",()=>void this.showTemplates(),"secondary","plus-alt2"),S("Export",()=>void this.exportForm(),"secondary","download"),S("Import",()=>void this.importForm(),"secondary","upload"),S("Preview",()=>void this.preview(),"secondary","visibility"),S("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 i=Ge(e,s.dataset.atfbBind??"");s.textContent!==i&&(s.textContent=i)}}syncInspector(e){if(this.selected===e.id)for(const t of this.inspector.querySelectorAll("[data-atfb-bind]")){const s=Ge(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 i=S(t,()=>this.travel(s),"secondary",e);return i.setAttribute("data-atfb-history",String(s)),i.disabled=!this.canTravel(s),i}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 i=await $.createForm({title:s.title??t.name.replace(/\.json$/i,""),schema:s.schema});this.forms.unshift({id:i.id,title:i.title,status:i.status,modified:i.modified,fields:i.schema.fields.length,theme:i.schema.settings.theme,entries:0,unread:0,views:0,submissions:0,shortcode:i.shortcode}),this.form=i,this.schema=i.schema,this.selected=null,this.dirty=!1,this.history=[],this.historyAt=-1,this.snapshot(),this.renderBar(),this.renderCanvas(),this.renderInspector(),N("Form imported",i.title)}catch(s){N("Could not import that file",s instanceof Error?s.message:"","error")}}),e.click()}saveButton(){const e=S("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 $.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 a=this.bar.querySelector("[data-atfb-save]");a&&(a.disabled=!0)}Vt(s.id);const i=this.forms.find(a=>a.id===s.id);i&&(i.title=s.title),ws(s.id,s.title,s.previewUrl),e||N("Form saved",s.title)}catch(s){N(Ae("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 open(e){if(!(this.dirty&&!await fe("You have unsaved changes. Discard them?"))){try{this.form=await $.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&&at(this.root,Ls(this.form,M?.adminUrl??""))}async showTemplates(){if(!this.config)return;const e=r("div",{class:"atfb-overlay"}),t=a=>{a.key==="Escape"&&s()},s=()=>{e.remove(),document.removeEventListener("keydown",t)},i=r("div",{class:"atfb-templates",children:this.config.templates.map(a=>r("button",{class:"atfb-template",type:"button",on:{click:async()=>{s();try{const o=await $.createForm({template:a.slug});this.forms.unshift({id:o.id,title:o.title,status:o.status,modified:o.modified,fields:o.schema.fields.length,theme:o.schema.settings.theme,entries:0,unread:0,views:0,submissions:0,shortcode:o.shortcode}),this.form=o,this.schema=o.schema,this.selected=null,this.dirty=!1,this.history=[],this.historyAt=-1,this.snapshot(),this.renderBar(),this.renderCanvas(),this.renderInspector(),this.announceIdentity()}catch(o){N("Could not create the form",o instanceof Error?o.message:"","error")}}},children:[I(a.icon),r("strong",{text:a.label}),r("span",{text:a.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"}),i,r("div",{class:"atfb-modal__actions",children:[S("Cancel",s)]})]})),e.addEventListener("click",a=>{a.target===e&&s()}),document.addEventListener("keydown",t),this.root.append(e),i.querySelector("button")?.focus()}renderFormsList(){F(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."}),S("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(`${M?.adminUrl??""}admin.php?page=allterrain-forms-entries`)}renderPalette(){if(!this.config)return;F(this.palette);const e=new Map;for(const s of this.config.fieldTypes){const i=e.get(s.group)??[];i.push(s),e.set(s.group,i)}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 i=s.target.value.toLowerCase().trim();this.palette.querySelectorAll(".atfb-chip").forEach(a=>{const o=(a.textContent??"").toLowerCase();a.hidden=i!==""&&!o.includes(i)}),this.palette.querySelectorAll(".atfb-group").forEach(a=>{const o=Array.from(a.querySelectorAll(".atfb-chip")).some(l=>!l.hidden);a.hidden=!o})}}});this.palette.append(t);for(const[s,i]of Object.entries(this.config.groups)){const a=e.get(s);a?.length&&this.palette.append(r("div",{class:"atfb-group",children:[r("h3",{class:"atfb-group__title",text:i}),r("div",{class:"atfb-group__items",children:a.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:[I(e.icon),r("span",{text:e.label})]});return t.addEventListener("pointerdown",s=>{const i=r("div",{class:"atfb-chip atfb-chip--ghost",children:[I(e.icon),r("span",{text:e.label})]});K().start({payload:Oe(ee,t,{fieldType:e.type,isNew:!0},s,i),origin:s,onClickOnly:()=>this.addField(e.type)})}),t.addEventListener("click",s=>{K().recentlyEndedDrag()&&s.preventDefault()}),t}renderCanvas(){if(F(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:Ae("emptyCanvas","Drag a field from the left to begin.")})),this.schema.fields.forEach((s,i)=>{e.append(this.renderFieldCard(s,i))});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:"),i=$e.some(b=>b.value===e.prefill),a=s?"query":i&&e.prefill||(e.prefill?"custom":""),o=r("div",{class:"atfb-prefill__detail"}),l=r("p",{class:"atfb-row__hint atfb-prefill__preview"}),c=b=>{if(o.replaceChildren(),l.replaceChildren(),b==="query"){const v=e.prefill.startsWith("query:")?e.prefill.slice(6):"";o.append(w(v,x=>{const k=x.trim();t("prefill",k?`query:${k}`:""),d(`query:${k}`)},"utm_source"),r("p",{class:"atfb-row__hint",text:"The name of the parameter in the link people arrive on."}))}b==="custom"&&o.append(w(e.prefill,v=>{t("prefill",v),d(v)},"myplugin:something"),r("p",{class:"atfb-row__hint",text:"For a source another plugin has added through atf_resolve_prefill."})),d(e.prefill)},d=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 v=$e.find(x=>x.value===b)?.tag;v&&we(this.form?.id??0).then(x=>{for(const k of x)for(const E of k.items)if(E.tag===v){const q=b.startsWith("user:");if(!E.sample){l.textContent="Empty unless the visitor is signed in.";return}l.textContent=q?`Opens with “${E.sample}” for you — empty for a visitor who is not signed in.`:`Opens with “${E.sample}”.`;return}})};c(a);const h=r("select",{class:"atfb-input atfb-select",on:{change:b=>{const v=b.target.value;t("prefill",v==="query"||v==="custom"?"":v),c(v)}}});h.append(r("option",{value:"",text:"Nothing — leave it empty",attrs:{selected:a===""}}));for(const b of $s){const v=document.createElement("optgroup");v.label=b;for(const x of $e.filter(k=>k.group===b))v.append(r("option",{value:x.value,text:x.label,attrs:{selected:x.value===a}}));h.append(v)}const _=document.createElement("optgroup");return _.label="From the link they arrived on",_.append(r("option",{value:"query",text:"A parameter in the web address",attrs:{selected:a==="query"}})),h.append(_,r("option",{value:"custom",text:"Something else (advanced)",attrs:{selected:a==="custom"}})),m("Pre-fill this with",r("div",{class:"atfb-prefill",children:[h,o,l]}),"What the box already contains when the form opens. They can still change it.")}renderCondition(e){const t=e.some(s=>s.kind==="field"&&s.missing);return r("span",{class:`atfb-cond${t?" is-broken":""}`,attrs:{"aria-label":We(e)},children:[I("randomize"),...e.map(s=>this.renderConditionToken(s))]})}renderConditionToken(e){return e.kind==="field"&&!e.missing?r("button",{class:"atfb-cond__chip atfb-cond__chip--field",type:"button",text:e.text,title:"Go to this question",on:{click:i=>{i.stopPropagation(),this.selectField(e.fieldId),this.canvas.querySelector(`[data-atfb-card="${CSS.escape(e.fieldId)}"]`)?.scrollIntoView({block:"nearest",behavior:"smooth"})}}}):r("span",{class:{verb:"atfb-cond__verb",field:"atfb-cond__chip atfb-cond__chip--missing",operator:"atfb-cond__op",value:"atfb-cond__chip atfb-cond__chip--value",join:"atfb-cond__join"}[e.kind],text:e.text})}section(e,t,s,i=!1){const a=r("details",{class:"atfb-section",attrs:{open:this.openSections.get(e)??i},children:[r("summary",{text:t}),...s]});return a.addEventListener("toggle",()=>this.openSections.set(e,a.open)),a}logicMapButton(){const e=be(this.schema?.fields??[]).length>0,t=S(this.logicMapOn?"Hide logic":"Show logic",()=>{this.logicMapOn=!this.logicMapOn,Tt(ot,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=be(t);if(!s.length||!this.logicMapOn)return;e.classList.add("has-logicmap");const i=new Ht(e);i.setEdges(s),i.highlight(this.selected??""),this.logicMap=i,e.addEventListener("pointerover",a=>{const o=a.target.closest("[data-atfb-card]");i.highlight(o?.dataset.atfbCard??this.selected??"")}),e.addEventListener("pointerleave",()=>i.highlight(this.selected??""))}renderFieldCard(e,t){const s=this.config?.fieldTypes.find(d=>d.type===e.type),i=this.selected===e.id,a=this.schema?.fields??[],o=Pt(e,a),l=Bt(a).get(e.id)??0,c=r("div",{class:`atfb-card${i?" is-selected":""}`,attrs:{"data-atfb-card":e.id,"data-index":t,tabindex:"0",role:"button","aria-pressed":i,"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:[I("menu")]}),r("div",{class:"atfb-card__body",children:[r("div",{class:"atfb-card__head",children:[I(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]}),Yt(e,s,{edit:d=>{const h=this.liveField(e.id);h&&(d(h),this.markDirty(),this.syncInspector(h))},restructure:d=>{const h=this.liveField(e.id);h&&(this.snapshot(),d(h),this.markDirty(),this.renderCanvas(),this.renderInspector())}}),o.length?this.renderCondition(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 c.addEventListener("click",d=>{d.target.closest(".atfb-card__actions")||K().recentlyEndedDrag()||this.selectField(e.id)}),c.addEventListener("keydown",d=>{if(d.key==="Enter"||d.key===" "){d.preventDefault(),this.selectField(e.id);return}d.altKey&&(d.key==="ArrowUp"||d.key==="ArrowDown")&&(d.preventDefault(),this.moveField(e.id,d.key==="ArrowUp"?t-1:t+1),window.requestAnimationFrame(()=>{this.canvas.querySelector(`[data-atfb-card="${CSS.escape(e.id)}"]`)?.focus()})),(d.key==="Delete"||d.key==="Backspace")&&(d.preventDefault(),this.deleteField(e.id))}),c.addEventListener("pointerdown",d=>{d.target.closest(".atfb-card__actions")||K().start({payload:Oe(ee,c,{fieldId:e.id,field:e,isNew:!1},d),origin:d})}),c}requiredToggle(e){return r("button",{class:`atfb-req${e.required?" is-on":""}`,type:"button",text:e.required?"Required":"Optional",title:e.required?"This must be answered. Click to make it optional.":"Click to make this required.",attrs:{"aria-pressed":e.required?"true":"false"},on:{pointerdown:s=>s.stopPropagation(),click:s=>{s.stopPropagation(),e.required=!e.required,this.markDirty(),this.renderCanvas(),this.renderInspector()}}})}cardAction(e,t,s){return r("button",{class:"atfb-card__action",type:"button",title:t,attrs:{"aria-label":t},on:{click:i=>{i.stopPropagation(),s()}},children:[I(e)]})}registerCanvasTarget(e){this.canvasTarget?.(),this.canvasTarget=null;const t=r("div",{class:"atfb-marker",attrs:{"aria-hidden":"true"}}),s=K().registerDropTarget({id:`atfb-canvas-${this.form?.id??0}`,element:e,accept:a=>a.type===ee,onEnter:()=>e.classList.add("is-dropping"),onLeave:()=>{e.classList.remove("is-dropping"),t.remove()},onDrop:(a,o)=>{e.classList.remove("is-dropping"),t.remove();const l=a.payload.data,c=l.fieldId?this.canvas.querySelector(`[data-atfb-card="${CSS.escape(l.fieldId)}"]`):null,d=qe(e,".atfb-card",o.clientY,c??void 0);if(l.isNew&&l.fieldType){this.addField(l.fieldType,d);return}if(l.fieldId&&this.schema?.fields.some(h=>h.id===l.fieldId)){this.moveField(l.fieldId,d);return}l.field&&this.insertField({...l.field,id:""},d)}}),i=a=>{const o=a.detail;if(o?.payload?.type!==ee||!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,c=o.clientY??0,d=qe(e,".atfb-card",c,l??void 0),h=Array.from(e.querySelectorAll(".atfb-card")).filter(_=>_!==l);d>=h.length?e.append(t):h[d].before(t)};document.addEventListener("os.drag.move",i),this.canvasTarget=()=>{s(),document.removeEventListener("os.drag.move",i)}}addField(e,t){const s=this.config?.fieldTypes.find(a=>a.type===e);if(!s||!this.schema)return;const i={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(i,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=ht(this.schema.fields,e,t);if(!s)return;this.snapshot();const[i]=this.schema.fields.splice(s.from,1);this.schema.fields.splice(s.to,0,i),this.markDirty(),this.renderCanvas()}duplicateField(e){if(!this.schema)return;const t=this.schema.fields.findIndex(i=>i.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(i=>i.logic?.rules?.some(a=>a.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.`:Ae("confirmDelete","Delete this? It cannot be undone.");await fe(s,"Delete field")&&(this.snapshot(),this.schema.fields=this.schema.fields.filter(i=>i.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(a=>a.id));let i=t.length+1;for(;s.has(`${e}${i}`);)i++;return`${e}${i}`}renderTypeSettings(e,t,s,i){for(const a of s){const o=lt[a];o&&(this.inspector.append(ct(e,o,i)),o.also&&this.inspector.append(ct(e,{...o,key:o.also.key,label:o.also.label,hint:o.also.hint},i)))}s.includes("rows")&&e.type!=="likert"&&this.inspector.append(m("Lines tall",R(String(e.rows??""),a=>i("rows",a)))),s.includes("rows")&&e.type==="likert"&&this.inspector.append(this.renderStatementList(e,i)),s.includes("parts")&&this.inspector.append(this.renderPartsEditor(e,t,i)),s.includes("default")&&!["files","object"].includes(t?.value??"")&&this.inspector.append(this.renderDefaultControl(e,t,i))}renderStatementList(e,t){const s=Array.isArray(e.rows)?e.rows:[];return m("Statements",re(s.map(i=>i.label??"").join(` -`),i=>t("rows",dt(s,i)),5),"One per line. Each becomes a row of the matrix.")}renderPartsEditor(e,t,s){const i=t?.parts??[],a=Array.isArray(e.parts)?e.parts:i.map(l=>l.key),o=i.map(l=>A(l.label,a.includes(l.key),c=>{const d=i.map(h=>h.key).filter(h=>h===l.key?c:a.includes(h));s("parts",d.length?d:[l.key])}));return r("div",{class:"atfb-section",children:[r("h4",{class:"atfb-section__title",text:"Parts to ask for"}),...o]})}renderDefaultControl(e,t,s){const i="Filled in before they start. They can change it.";return e.type==="switch"||e.type==="consent"?A("On by default",!!e.default,a=>s("default",a)):t?.choices&&(e.choices??[]).length?m("Default answer",D(String(e.default??""),[{value:"",label:"Nothing chosen"},...(e.choices??[]).map(a=>({value:String(a.value),label:a.label||String(a.value)}))],a=>s("default",a)),i):m("Default answer",w(String(e.default??""),a=>s("default",a)),i)}renderChoicesEditor(e,t){const s=e.choices??[],i=o=>this.liveField(e.id)?.choices?.[o],a=r("div",{class:"atfb-choices"});return s.forEach((o,l)=>{const c=r("div",{class:"atfb-choice-row",children:[e.type==="image_choice"?this.choiceImageWell(o,l,e):null,H(w(o.label,d=>{const h=i(l);if(!h)return;const _=!h.value||h.value===h.label;h.label=d,_&&(h.value=d),this.markDirty();const b=this.liveField(e.id);b&&this.syncCanvas(b)}),`choice:${l}:label`),H(w(o.value,d=>{const h=i(l);h&&(h.value=d,this.markDirty())},"value"),`choice:${l}:value`),e.type==="quiz"||o.points!==void 0?R(String(o.points??""),d=>{const h=i(l);h&&(h.points=d===""?void 0:Number(d),this.markDirty())}):R(String(o.price??""),d=>{const h=i(l);h&&(h.price=d===""?void 0:Number(d),this.markDirty())}),r("button",{class:"atfb-card__action",type:"button",attrs:{"aria-label":`Remove ${o.label}`},on:{click:()=>{const d=this.liveField(e.id);d&&((d.choices??[]).splice(l,1),t("choices",d.choices),this.renderInspector())}},children:[I("trash")]})]});a.append(c)}),r("div",{class:"atfb-section",children:[r("h4",{text:"Choices"}),r("p",{class:"atfb-hint",text:e.type==="quiz"?"Label, value, points.":"Label, value, and a price for calculations."}),a,S("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",D(String(e.correct??""),[{value:"",label:"—"},...s.map(o=>({value:o.value,label:o.label}))],o=>t("correct",o))):null]})}choiceImageWell(e,t,s){const i=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}`}):I("format-image")]}),a=K().registerDropTarget({id:`atfb-well-${s.id}-${t}`,element:i,accept:o=>Cs.includes(o.type),onEnter:()=>i.classList.add("is-dropping"),onLeave:()=>i.classList.remove("is-dropping"),onDrop:o=>{i.classList.remove("is-dropping");const l=o.payload.data,c=Number(l.attachmentId??l.id??l.file?.id??0);if(!c){N("That is not an image this field can use","","error");return}const d=this.liveField(s.id)?.choices?.[t];d&&(d.image=c,this.markDirty(),this.renderInspector())}});return this.teardowns.push(a),i.addEventListener("click",()=>{const o=this.liveField(s.id)?.choices?.[t];o?.image&&(o.image=void 0,this.markDirty(),this.renderInspector())}),i}renderValidationSection(e,t,s){const i=[],a=[];t.includes("minlength")&&a.push(["minlength","Minimum characters"],["maxlength","Maximum characters"]),t.includes("min")?a.push(["min","Minimum"],["max","Maximum"]):t.includes("max")&&a.push(["max","Highest"]),t.includes("step")&&a.push(["step","Steps of"]),t.includes("mindate")&&a.push(["minDate","Earliest date"],["maxDate","Latest date"]),t.includes("mintime")&&a.push(["minTime","Earliest time"],["maxTime","Latest time"]);for(const[l,c]of a)i.push(m(c,w(String(e[l]??""),d=>s(l,d))));t.includes("pattern")&&i.push(m("Pattern",w(String(e.pattern??""),l=>s("pattern",l)),"A regular expression, without slashes.")),t.includes("unique")&&i.push(A("No two people may submit the same value",!!e.unique,l=>s("unique",l)));const o=e.messages??{};return t.includes("required")&&i.push(m("Message when required",w(o.required??"",l=>{o.required=l,s("messages",o)}),"Leave empty for the default wording.")),i.length?this.section(`validation:${e.id}`,"Validation",i):r("div",{class:"atfb-section is-empty"})}renderLogicSection(e,t){const s=e.logic,i=()=>this.liveField(e.id)?.logic,a=(this.schema?.fields??[]).filter(l=>l.id!==e.id&&l.type!=="page_break"),o=r("div",{class:"atfb-rules"});return s.rules.forEach((l,c)=>{o.append(r("div",{class:"atfb-rule",children:[D(l.field,a.map(d=>({value:d.id,label:d.label||d.id})),d=>{const h=i()?.rules[c];h&&(h.field=d,this.markDirty())}),D(l.operator,Object.entries(this.config?.operators??{}).map(([d,h])=>({value:d,label:h})),d=>{const h=i()?.rules[c];h&&(h.operator=d,this.markDirty())}),w(l.value,d=>{const h=i()?.rules[c];h&&(h.value=d,this.markDirty())}),r("button",{class:"atfb-card__action",type:"button",attrs:{"aria-label":"Remove this rule"},on:{click:()=>{const d=i();d&&(d.rules.splice(c,1),t("logic",d),this.renderInspector())}},children:[I("trash")]})]}))}),this.section(`logic:${e.id}`,"Conditional logic",[A("Only show this field sometimes",s.enabled,l=>{const c=i();c&&(c.enabled=l,t("logic",c),this.renderInspector())}),s.enabled?r("div",{children:[r("div",{class:"atfb-rule-head",children:[D(s.action,[{value:"show",label:"Show"},{value:"hide",label:"Hide"}],l=>{const c=i();c&&(c.action=l,t("logic",c))}),r("span",{text:"this field when"}),D(s.match,[{value:"all",label:"all"},{value:"any",label:"any"}],l=>{const c=i();c&&(c.match=l,t("logic",c))}),r("span",{text:"of these match:"})]}),o,S("Add rule",()=>{const l=i();l&&(l.rules.push({field:a[0]?.id??"",operator:"is",value:""}),t("logic",l),this.renderInspector())},"ghost","plus-alt2")]}):null],s.enabled)}renderTabCanvas(){return this.schema?this.tab==="theme"?Qe({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 i=/