From d06e9aa7feaf5da8a91103870593203188e5d989 Mon Sep 17 00:00:00 2001 From: prismiwi2015 Date: Fri, 21 Aug 2026 23:42:35 +0200 Subject: [PATCH] Forms learn to celebrate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A success screen designer on every message confirmation: nine styles (plain, simple, minimalistic, card, check mark, confetti, fireworks, sparkles, typewriter), each configurable — heading with merge tags, emoji, accent, intensity, an optional fill-it-in-again button — with a live full-screen preview in the builder that runs the visitor's own renderer, and a static no-JavaScript fallback that shares its markup. Analytics learn what a visitor holds: aggregate device / browser / OS tallies (counters over coarse classes, never a user-agent string or per-visitor row) with per-device conversion in a new report panel, per-form privacy switches, an atf_record_tech veto filter, refreshed privacy-policy text, and a demo population that browses realistically enough for the mobile conversion gap to be a true finding. Repeaters stop shouting: a failing row names its exact control (dotted rep.row.sub error keys, client and server), the container wears a ring and the failing box the red, the summary indents a repeater's problems under its own line, and the add button gets back the breathing room the button reset had quietly taken. Also: star ratings no longer light all five from the hover-in-the-gaps rule (and no longer display a three as a two), and button label colours survive site themes that repaint button:hover. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0119U4sRRWGcTQdwYreTwpAp --- allterrain-forms.php | 9 + assets/css/analytics.css | 27 ++ assets/css/builder.css | 141 ++++++ assets/css/form.css | 384 +++++++++++++++- assets/js/analytics.js | 62 +++ assets/js/analytics.min.js | 2 +- assets/js/builder.js | 618 +++++++++++++++++++++++++- assets/js/builder.min.js | 6 +- assets/js/form.js | 470 +++++++++++++++++++- assets/js/form.min.js | 2 +- docs/architecture.md | 4 +- docs/hooks-reference.md | 40 +- includes/analytics.php | 334 ++++++++++++++ includes/confirmations.php | 182 +++++++- includes/demo-data.php | 124 +++++- includes/post-types.php | 12 + includes/privacy.php | 3 +- includes/render-controls.php | 40 +- includes/render.php | 80 +++- includes/schema.php | 51 +++ includes/shortcode.php | 3 + includes/validation.php | 61 ++- src/analytics.ts | 79 +++- src/builder.ts | 299 ++++++++++++- src/form.ts | 168 ++++++- src/success.ts | 542 ++++++++++++++++++++++ src/types.ts | 48 ++ tests/phpunit/tests/analytics.php | 190 +++++++- tests/phpunit/tests/render.php | 96 +++- tests/phpunit/tests/successScreen.php | 216 +++++++++ tests/phpunit/tests/validation.php | 104 +++++ tests/vitest/form-enhance.test.ts | 152 +++++++ tests/vitest/success.test.ts | 157 +++++++ 33 files changed, 4604 insertions(+), 102 deletions(-) create mode 100644 src/success.ts create mode 100644 tests/phpunit/tests/successScreen.php create mode 100644 tests/vitest/success.test.ts diff --git a/allterrain-forms.php b/allterrain-forms.php index e94a56e..67d5d6d 100644 --- a/allterrain-forms.php +++ b/allterrain-forms.php @@ -142,6 +142,15 @@ /** Rolling per-form counters for views, starts and submissions. */ define( 'ATF_META_STATS', '_atf_stats' ); +/** + * Rolling per-form device / browser / OS tallies. + * + * Aggregate counters keyed by coarse class ("mobile", "chrome"), one set for + * views and one for submissions -- never a per-visitor row, and never the + * user-agent string itself. + */ +define( 'ATF_META_TECH', '_atf_tech' ); + /** * A partially completed submission, kept so the user can resume it. * diff --git a/assets/css/analytics.css b/assets/css/analytics.css index b40764c..5fd7d87 100644 --- a/assets/css/analytics.css +++ b/assets/css/analytics.css @@ -482,3 +482,30 @@ transition: none; } } + +/* --- Devices and browsers -------------------------------------------------- */ + +.atfa-tech { + display: grid; + /* auto-fit rather than a fixed three: the window this lives in can be + narrow, and columns that stack beat columns that crush. */ + grid-template-columns: repeat( auto-fit, minmax( 210px, 1fr ) ); + gap: 20px; +} + +.atfa-tech__col { + min-inline-size: 0; +} + +.atfa-tech__title { + margin: 0 0 8px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var( --atfb-muted ); +} + +.atfa-tech .atfa-bars__row { + grid-template-columns: minmax( 64px, 110px ) 1fr auto; +} diff --git a/assets/css/builder.css b/assets/css/builder.css index ecfa406..ae7ef0e 100644 --- a/assets/css/builder.css +++ b/assets/css/builder.css @@ -3114,3 +3114,144 @@ os-checkbox-label.atfb-check { text-overflow: ellipsis; white-space: nowrap; } + +/* -------------------------------------------------------------------------- */ +/* The success screen designer */ +/* -------------------------------------------------------------------------- */ + +.atfb-success { + margin-block-start: 18px; + padding-block-start: 14px; + border-block-start: 1px solid var( --atfb-border ); +} + +.atfb-success__head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-block-end: 10px; +} + +.atfb-success__head h4 { + margin: 0; + font-size: 13px; +} + +/* The gallery: every style is a little stage with its glyph on it. A grid of + cards, not a dropdown — a look has to be seen to be chosen. */ +.atfb-success__styles { + display: grid; + grid-template-columns: repeat( 3, 1fr ); + gap: 8px; + margin-block-end: 14px; +} + +.atfb-success__style { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + padding: 10px 6px; + border: 1px solid var( --atfb-border ); + border-radius: var( --atfb-radius ); + background: var( --atfb-bg ); + cursor: pointer; + transition: border-color 0.12s ease, box-shadow 0.12s ease, transform 0.12s ease; +} + +.atfb-success__style:hover { + border-color: var( --atfb-accent ); + transform: translateY( -1px ); +} + +.atfb-success__style.is-selected { + border-color: var( --atfb-accent ); + box-shadow: 0 0 0 2px var( --atfb-accent-soft ); + background: var( --atfb-well ); +} + +.atfb-success__style-glyph { + font-size: 20px; + line-height: 1.2; +} + +.atfb-success__style-label { + font-size: 11px; + color: var( --atfb-muted ); +} + +.atfb-success__style.is-selected .atfb-success__style-label { + color: inherit; + font-weight: 600; +} + +.atfb-success__accent-row { + display: flex; + align-items: center; + gap: 8px; +} + +.atfb-success__accent { + inline-size: 44px; + block-size: 30px; + padding: 2px; + border: 1px solid var( --atfb-border ); + border-radius: var( --atfb-radius ); + background: var( --atfb-bg ); + cursor: pointer; +} + +/* The preview: a dark stage over the whole builder with the real screen on + it, so confetti has a page to fall over and fireworks have a sky. */ +.atfb-success-preview { + position: fixed; + inset: 0; + z-index: 9999; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 18px; + padding: 24px; + background: rgba( 12, 16, 26, 0.72 ); + backdrop-filter: blur( 3px ); + animation: atfb-success-preview-in 0.2s ease-out; +} + +.atfb-success-preview__stage { + inline-size: min( 620px, 100% ); + max-block-size: 80vh; + overflow: auto; +} + +.atfb-success-preview__bar { + display: flex; + gap: 8px; + /* A solid chip under the controls. The secondary button variant is drawn + for a light UI surface; floated straight over the dark backdrop it + turned half-transparent and unreadable. */ + padding: 8px 10px; + border-radius: 12px; + background: var( --atfb-bg ); + border: 1px solid var( --atfb-border ); + box-shadow: 0 8px 24px rgba( 0, 0, 0, 0.35 ); +} + +@keyframes atfb-success-preview-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@media ( prefers-reduced-motion: reduce ) { + .atfb-success__style, + .atfb-success-preview { + transition: none; + animation: none; + } +} diff --git a/assets/css/form.css b/assets/css/form.css index 16ba177..80b72e4 100644 --- a/assets/css/form.css +++ b/assets/css/form.css @@ -694,22 +694,32 @@ ); } -/* Every star up to and including the checked one lights up. `:has()` reads - forwards, which is the direction this needs and the reason it cannot be done - with a sibling combinator alone. */ +/* The fill, twice over: once for the checked state, once for the hover + preview. Each is "light everything, then unlight what comes after" — + `:has()` reads forwards for the first half, a sibling combinator for the + second. All four rules weigh the same (0,4,0), so source order is the + arbitration: hover, declared last, wins while a star is under the pointer. + + Two traps this shape avoids. The unlight half must start *after the checked + star's own label* (`:checked + label ~ label`) — the label follows its + input, so the shorter `:checked ~ label` unlights the star that was just + clicked and shows a three as a two. And the hover half must key on a *star* + being hovered, not the row: `.atf-rating__stars:hover` is true in the gaps + between stars and stays true after a click, which lit all five whatever was + chosen. */ .atf-rating__stars:has( .atf-rating__input:checked ) .atf-rating__star { background: var( --atf-accent ); } -.atf-rating__input:checked ~ .atf-rating__star { +.atf-rating__input:checked + .atf-rating__star ~ .atf-rating__star { background: var( --atf-border ); } -.atf-rating__stars:hover .atf-rating__star { +.atf-rating__stars:has( .atf-rating__star:hover ) .atf-rating__star { background: var( --atf-accent ); } -.atf-rating__star:hover ~ .atf-rating__star { +.atf-rating__stars .atf-rating__star:hover ~ .atf-rating__star { background: var( --atf-border ); } @@ -988,8 +998,13 @@ /* Breathing room the button was missing: it sits under the row cards, not glued to them, and never stretches to the form's full width — themes whose - `--atf-button-width` is 100% mean the submit button, not this one. */ -.atf-repeater__add { + `--atf-button-width` is 100% mean the submit button, not this one. + + Two class names, not one: the `:where( button )` reset further down weighs + (0,1,0) and zeroes every button margin, and it appears later in the file — + so at equal weight this rule loses on source order and the button ends up + glued to the last row card after all. */ +.atf-repeater .atf-repeater__add { margin-block-start: 18px; inline-size: auto; } @@ -1132,6 +1147,24 @@ box-shadow: none; } +/* The label's colour, re-asserted for every interactive state. + * + * The `:where( button )` reset above covers the *base* state, but a site + * theme's `button:hover { color: #fff }` weighs (0,1,1) and outranks any + * single-class rule of ours — which painted the ghost "Add another" label + * white onto its own pale accent-soft hover, unreadable. Scoping under + * `.atf-form` lifts these to (0,2,0)+, the same shield the control rules use, + * and `:is()` covers focus and active where themes routinely colour those + * too. */ +.atf-form .atf-button:is( :hover, :focus, :active ):not( :disabled ) { + color: var( --atf-button-text ); +} + +.atf-form .atf-button--secondary:is( :hover, :focus, :active ):not( :disabled ), +.atf-form .atf-button--ghost:is( :hover, :focus, :active ):not( :disabled ) { + color: var( --atf-text ); +} + .atf-button__spinner { display: none; inline-size: 1em; @@ -1274,6 +1307,15 @@ padding-inline-start: 1.2em; } +/* A repeater's failing boxes, indented under the repeater's own line so the + list reads as one question with its problem rows, not three unrelated + problems. */ +.atf-errors .atf-errors__sub { + margin-block: 4px 6px; + padding-inline-start: 1.4em; + list-style: circle; +} + .atf-errors a { color: var( --atf-error ); } @@ -1284,18 +1326,34 @@ color: var( --atf-error ); } -.atf-field.has-error .atf-input, -.atf-field.has-error .atf-file__input { +/* `:not(--repeater)` because a repeater's wrapper going into error must not + bleed red into every control in every row — its rows mark their own failing + boxes, each a little `.atf-field` of its own that these rules then cover. */ +.atf-field.has-error:not( .atf-field--repeater ) .atf-input, +.atf-field.has-error:not( .atf-field--repeater ) .atf-file__input { border-color: var( --atf-error ); } /* An invalid field's own ring beats the shared focus ring, so a field in error focuses in red rather than in the accent colour. */ -.atf-field.has-error .atf-input:focus { +.atf-field.has-error:not( .atf-field--repeater ) .atf-input:focus { outline-color: var( --atf-error ); box-shadow: none; } +/* A repeater in error keeps the red where it belongs: a ring around the whole + container, the failing row's card border, and the failing control — never + every box in every row. The ring is a shadow, not a border, so nothing + shifts when it appears. */ +.atf-field--repeater.has-error .atf-repeater__rows { + box-shadow: 0 0 0 2px var( --atf-error ); + border-radius: var( --atf-radius-field ); +} + +.atf-repeater__row.has-error { + border-color: var( --atf-error ); +} + .atf-notice { padding: 14px 16px; border-radius: var( --atf-radius-field ); @@ -1330,6 +1388,310 @@ outline-offset: var( --atf-focus-ring-offset ); } +/* -------------------------------------------------------------------------- */ +/* Success screens */ +/* -------------------------------------------------------------------------- */ + +/* The styled screens replace the confirmation's quiet left-edge treatment + with their own composition; `plain` keeps the block above untouched. */ +.atf-success { + position: relative; + overflow: hidden; + padding: 36px 28px; + border-inline-start: 0; + text-align: center; +} + +.atf-success__inner { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + max-inline-size: 46ch; + margin-inline: auto; +} + +.atf-success__icon { + font-size: 44px; + line-height: 1; +} + +.atf-success__title { + margin: 0; + font-family: var( --atf-font-family-heading ); + font-size: var( --atf-size-heading ); + font-weight: var( --atf-weight-heading ); + color: var( --atf-heading ); +} + +.atf-success__message { + color: var( --atf-text ); +} + +.atf-success__message > :first-child { + margin-block-start: 0; +} + +.atf-success__message > :last-child { + margin-block-end: 0; +} + +.atf-success .atf-success__again { + margin-block-start: 10px; + inline-size: auto; +} + +/* Simple: everything drifts gently up into place. */ +.atf-success--simple .atf-success__inner { + animation: atf-success-rise 0.5s ease-out both; +} + +.atf-success--simple .atf-success__icon { + display: grid; + place-items: center; + inline-size: 64px; + block-size: 64px; + border-radius: 50%; + font-size: 30px; + color: var( --atf-accent-text ); + background: var( --atf-accent ); + animation: atf-success-pop 0.55s cubic-bezier( 0.34, 1.56, 0.64, 1 ) 0.15s both; +} + +/* Minimalistic: quiet type, generous space, one slow fade. */ +.atf-success--minimal { + padding: 72px 28px; + background: var( --atf-bg ); +} + +.atf-success--minimal .atf-success__inner { + gap: 18px; + animation: atf-success-fade 1.1s ease-out both; +} + +.atf-success--minimal .atf-success__title { + font-weight: 400; + letter-spacing: 0.04em; +} + +.atf-success--minimal .atf-success__message { + color: var( --atf-text-muted ); +} + +.atf-success--minimal .atf-success__icon { + font-size: 22px; + color: var( --atf-text-muted ); +} + +/* Card: an elevated panel with an accent crown that pops into place. */ +.atf-success--card { + background: var( --atf-surface ); + box-shadow: var( --atf-shadow-card ); + border-block-start: 5px solid var( --atf-accent ); + animation: atf-success-pop 0.5s cubic-bezier( 0.34, 1.56, 0.64, 1 ) both; +} + +.atf-success--card .atf-success__icon { + display: grid; + place-items: center; + inline-size: 72px; + block-size: 72px; + border-radius: 50%; + font-size: 34px; + background: var( --atf-accent-soft ); + animation: atf-success-pop 0.6s cubic-bezier( 0.34, 1.56, 0.64, 1 ) 0.2s both; +} + +/* Check mark: the ring and the tick draw themselves, then the words follow. */ +.atf-success__check { + inline-size: 84px; + block-size: 84px; + stroke: var( --atf-success ); + stroke-width: 3; + stroke-linecap: round; + stroke-linejoin: round; +} + +.atf-success__check-ring { + stroke-dasharray: 151; + stroke-dashoffset: 151; + animation: atf-success-draw 0.7s ease-out forwards; +} + +.atf-success__check-mark { + stroke-dasharray: 36; + stroke-dashoffset: 36; + animation: atf-success-draw 0.4s ease-out 0.65s forwards; +} + +.atf-success--check .atf-success__title, +.atf-success--check .atf-success__message, +.atf-success--check .atf-success__again { + animation: atf-success-rise 0.5s ease-out 0.9s both; +} + +/* Confetti and sparkles share the celebratory entrance; the particles are the + bundle's work, drawn on a page-level canvas or floated as spans. */ +.atf-success--confetti .atf-success__inner, +.atf-success--sparkles .atf-success__inner { + animation: atf-success-pop 0.55s cubic-bezier( 0.34, 1.56, 0.64, 1 ) both; +} + +.atf-success--confetti .atf-success__icon, +.atf-success--sparkles .atf-success__icon { + animation: atf-success-pop 0.6s cubic-bezier( 0.34, 1.56, 0.64, 1 ) 0.2s both; +} + +/* Fireworks: a dark stage whatever the theme, so the show reads. */ +.atf-success--fireworks { + background: #10141f; + color: #e2e8f0; + padding: 48px 28px; +} + +.atf-success--fireworks .atf-success__title { + color: #f8fafc; + text-shadow: 0 0 18px rgba( 120, 170, 255, 0.45 ); +} + +.atf-success--fireworks .atf-success__message { + color: #cbd5e1; +} + +.atf-success--fireworks .atf-success__inner { + animation: atf-success-glow 1.4s ease-out both; +} + +.atf-success--fireworks .atf-success__again { + color: #e2e8f0; + border-color: #475569; + background: transparent; +} + +/* Typewriter: monospace with a blinking caret while the bundle types. */ +.atf-success--typewriter { + text-align: start; +} + +.atf-success--typewriter .atf-success__inner { + align-items: flex-start; +} + +.atf-success--typewriter .atf-success__message { + font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; + min-block-size: 1.5em; +} + +.atf-success--typewriter .atf-success__message.is-typing::after { + content: '▋'; + margin-inline-start: 2px; + animation: atf-success-caret 0.8s step-end infinite; +} + +/* The celebration canvas covers the page, catches nothing, and is removed by + the bundle the moment the last particle dies. Not scoped to `.atf-form` on + purpose — confetti falling over the whole page is the effect. */ +.atf-success-canvas { + position: fixed; + inset: 0; + inline-size: 100vw; + block-size: 100vh; + pointer-events: none; + z-index: 99999; +} + +/* One floating particle, spawned by the sparkles effect. */ +.atf-success__spark { + position: absolute; + inset-block-end: -28px; + animation: atf-success-float 3s ease-out both; + pointer-events: none; +} + +@keyframes atf-success-rise { + from { + opacity: 0; + transform: translateY( 14px ); + } + + to { + opacity: 1; + transform: none; + } +} + +@keyframes atf-success-fade { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@keyframes atf-success-pop { + from { + opacity: 0; + transform: scale( 0.55 ); + } + + to { + opacity: 1; + transform: scale( 1 ); + } +} + +@keyframes atf-success-draw { + to { + stroke-dashoffset: 0; + } +} + +@keyframes atf-success-glow { + from { + opacity: 0; + filter: brightness( 2.2 ); + } + + to { + opacity: 1; + filter: none; + } +} + +@keyframes atf-success-caret { + 50% { + opacity: 0; + } +} + +@keyframes atf-success-float { + from { + opacity: 0; + transform: translateY( 0 ) rotate( -8deg ); + } + + 12% { + opacity: 1; + } + + to { + opacity: 0; + transform: translateY( -420px ) rotate( 10deg ); + } +} + +/* The visitor asked for calm; every entrance settles instantly and the bundle + skips its particle systems entirely. */ +@media ( prefers-reduced-motion: reduce ) { + .atf-success *, + .atf-success { + animation-duration: 0.01ms !important; + animation-delay: 0s !important; + } +} + /* -------------------------------------------------------------------------- */ /* The honeypot */ /* -------------------------------------------------------------------------- */ diff --git a/assets/js/analytics.js b/assets/js/analytics.js index cd24ece..5b65430 100644 --- a/assets/js/analytics.js +++ b/assets/js/analytics.js @@ -474,6 +474,9 @@ var allTerrainFormsAnalytics = function(exports) { ); } else { this.body.append(this.timeline(report)); + if (report.tech) { + this.body.append(this.techPanel(report.tech)); + } for (const field of report.fields) { if (field.nps) { this.body.append(this.npsPanel(field)); @@ -617,6 +620,65 @@ var allTerrainFormsAnalytics = function(exports) { }) ]); } + /** + * Where submissions come from: device, browser, operating system. + * + * Three columns of the same shape so the eye can rake across them. The + * numbers are aggregate tallies kept by the server — no visitor rows exist + * to sample — and the device column carries the one derived figure worth a + * highlight: per-device conversion, which is where "the phone layout is + * losing people" first becomes visible. + */ + techPanel(tech) { + const facets = [ + { key: "device", label: "Device" }, + { key: "browser", label: "Browser" }, + { key: "os", label: "System" } + ]; + const column = (facet) => { + const rows = tech[facet.key]; + const ceiling = rows.reduce((most, row) => Math.max(most, row.share), 0); + return el("div", { + class: "atfa-tech__col", + children: [ + el("h4", { class: "atfa-tech__title", text: facet.label }), + el("ul", { + class: "atfa-bars", + children: rows.map( + (row) => el("li", { + class: "atfa-bars__row", + children: [ + el("span", { class: "atfa-bars__label", text: row.label }), + el("div", { + class: "atfa-meter", + children: [ + el("div", { + class: "atfa-meter__fill", + attrs: { style: `width:${ceiling ? row.share / ceiling * 100 : 0}%` } + }) + ] + }), + el("span", { + class: "atfa-bars__value", + text: null === row.conversion ? `${row.share}%` : `${row.share}% → ${row.conversion}%`, + attrs: null === row.conversion ? { title: `${row.submissions} submissions` } : { + title: `${row.submissions} submissions from ${row.views} views — ${row.conversion}% converted` + } + }) + ] + }) + ) + }) + ] + }); + }; + const hasConversion = facets.some((facet) => tech[facet.key].some((row) => null !== row.conversion)); + return this.panel( + "Devices and browsers", + hasConversion ? "Share of submissions, and → the conversion rate for that class. Aggregate tallies — nothing is stored per visitor." : "Share of submissions. Aggregate tallies — nothing is stored per visitor.", + [el("div", { class: "atfa-tech", children: facets.map(column) })] + ); + } /** * The cross-tab. * diff --git a/assets/js/analytics.min.js b/assets/js/analytics.min.js index 1a1cb01..4c5e373 100644 --- a/assets/js/analytics.min.js +++ b/assets/js/analytics.min.js @@ -1 +1 @@ -var allTerrainFormsAnalytics=function(p){"use strict";function A(){return window.wp?.os??null}const d=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(!d?.restUrl)throw new y("AllTerrain Forms is not configured on this page.",0);const t=T(d.restUrl,s),n={"Content-Type":"application/json",...e.headers??{}};d.nonce&&(n["X-WP-Nonce"]=d.nonce);const o=A(),r=await(o?.fetch?(l,u)=>o.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 y(l,r.status,u)}if(r.status!==204)return await r.json()}const c=s=>M(s);async function D(s){if(!d?.wpRestUrl)throw new y("AllTerrain Forms is not configured on this page.",0);const e=d.nonce?{"X-WP-Nonce":d.nonce}:{},t=A(),n=T(d.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 m=(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:()=>c("/config"),listForms:()=>c("/forms"),listArchivedForms:()=>c("/forms?archived=1"),archiveForm:s=>m(`/forms/${s}/archive`,{}),unarchiveForm:s=>m(`/forms/${s}/unarchive`,{}),getForm:s=>c(`/forms/${s}`).then(g),createForm:s=>m("/forms",s).then(g),updateForm:(s,e)=>m(`/forms/${s}`,e).then(g),duplicateForm:s=>m(`/forms/${s}/duplicate`,{}).then(g),deleteForm:s=>w(`/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=>c(`/forms/${s}/merge-tags`).then(e=>e.groups),analytics:(s,e="")=>c(`/forms/${s}/analytics${e?`?dimension=${encodeURIComponent(e)}`:""}`),demoStatus:()=>c("/demo"),demoSeed:s=>m("/demo",s?{count:s}:{}),demoRemove:()=>w("/demo"),listEntries:s=>c(`/entries${C(s)}`),getEntry:s=>c(`/entries/${s}`),updateEntry:(s,e)=>m(`/entries/${s}`,e),deleteEntry:s=>w(`/entries/${s}`),exportEntries:s=>c(`/entries/export${C(s)}`),listThemes:()=>c("/themes"),saveTheme:s=>m("/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()}`,"",[this.scrollable(a("table",{class:"atfa-table",children:[a("thead",{children:[o]}),a("tbody",{children:i})]}))])}questions(e){return a("div",{class:"atfa-questions",children:e.fields.map(t=>this.question(t,e.sampled))})}question(e,t){const n=[];if(e.choices.length){const o=e.choices.reduce((i,r)=>Math.max(i,r.count),0);n.push(a("ul",{class:"atfa-bars",children:e.choices.map(i=>a("li",{class:"atfa-bars__row",children:[a("span",{class:"atfa-bars__label",text:i.label}),a("div",{class:"atfa-meter",children:[a("div",{class:"atfa-meter__fill",attrs:{style:`width:${o?i.count/o*100:0}%`}})]}),a("span",{class:"atfa-bars__value",text:`${i.count} ${e.answered?Math.round(i.count/e.answered*100):0}%`})]}))}))}return e.numbers&&n.push(this.histogram(e)),n.length||n.push(a("p",{class:"atfa-question__note",text:`${e.answered} of ${e.of??t} ${e.unit??"people"} answered this.`})),this.panel(e.label,this.questionSummary(e,t),n)}questionSummary(e,t){const n=[`${e.rate}% answered`];return e.numbers&&n.push(`mean ${e.numbers.mean}`,`median ${e.numbers.median}`),n.push(`${e.answered}/${e.of??t}`),n.join(" · ")}histogram(e){const t=e.numbers;if(!t)return a("div");const n=L(t),o=n.reduce((i,r)=>Math.max(i,r.count),0);return this.scrollable(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})]}))}))}scrollable(e){return a("div",{class:"atfa-scroll",children:[e]})}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 F=!1;return document.addEventListener("atf-open-demo-panel",()=>{F=!0,window.setTimeout(()=>{b?.revealDemo(),F=!1},600)}),document.addEventListener("os-window-content-loaded",()=>{S(),F&&window.setTimeout(()=>b?.revealDemo(),400)}),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",S):S(),p.MAX_HISTOGRAM_BUCKETS=q,p.histogramBuckets=L,p.mountAnalytics=S,Object.defineProperty(p,Symbol.toStringTag,{value:"Module"}),p}({}); +var allTerrainFormsAnalytics=function(p){"use strict";function F(){return window.wp?.os??null}const m=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(!m?.restUrl)throw new y("AllTerrain Forms is not configured on this page.",0);const t=T(m.restUrl,s),n={"Content-Type":"application/json",...e.headers??{}};m.nonce&&(n["X-WP-Nonce"]=m.nonce);const i=F(),r=await(i?.fetch?(l,c)=>i.fetch(l,c,{source:"allterrain-forms"}):(l,c)=>fetch(l,c))(t,{credentials:"same-origin",...e,headers:n});if(!r.ok){let l=`Request failed with status ${r.status}.`,c="";try{const d=await r.json();l=d.message??l,c=d.code??""}catch{}throw new y(l,r.status,c)}if(r.status!==204)return await r.json()}const h=s=>M(s);async function D(s){if(!m?.wpRestUrl)throw new y("AllTerrain Forms is not configured on this page.",0);const e=m.nonce?{"X-WP-Nonce":m.nonce}:{},t=F(),n=T(m.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 y(`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 u=(s,e)=>M(s,{method:"POST",body:JSON.stringify(e)}),w=s=>M(s,{method:"DELETE"});function P(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 g(s){const e=s?.schema?.settings;return e&&(!e.themeOverrides||Array.isArray(e.themeOverrides))&&(e.themeOverrides={...e.themeOverrides}),s}const f={config:()=>h("/config"),listForms:()=>h("/forms"),listArchivedForms:()=>h("/forms?archived=1"),archiveForm:s=>u(`/forms/${s}/archive`,{}),unarchiveForm:s=>u(`/forms/${s}/unarchive`,{}),getForm:s=>h(`/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=>h(`/forms/${s}/merge-tags`).then(e=>e.groups),analytics:(s,e="")=>h(`/forms/${s}/analytics${e?`?dimension=${encodeURIComponent(e)}`:""}`),demoStatus:()=>h("/demo"),demoSeed:s=>u("/demo",s?{count:s}:{}),demoRemove:()=>w("/demo"),listEntries:s=>h(`/entries${P(s)}`),getEntry:s=>h(`/entries/${s}`),updateEntry:(s,e)=>u(`/entries/${s}`,e),deleteEntry:s=>w(`/entries/${s}`),exportEntries:s=>h(`/entries/export${P(s)}`),listThemes:()=>h("/themes"),saveTheme:s=>u("/themes",s),deleteTheme:s=>w(`/themes/${s}`)};function C(s){return`allterrain-forms/requested-form-${s}`}function R(s){try{const e=window.sessionStorage.getItem(C(s));return window.sessionStorage.removeItem(C(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,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 _(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 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 I(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 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(o-1,Math.max(0,Math.floor((d-e)/i))),l=[];for(let d=0;dt.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)),e.tech&&this.body.append(this.techPanel(e.tech));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:O.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:O.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})]}))})]})]})])}techPanel(e){const t=[{key:"device",label:"Device"},{key:"browser",label:"Browser"},{key:"os",label:"System"}],n=o=>{const r=e[o.key],l=r.reduce((c,d)=>Math.max(c,d.share),0);return a("div",{class:"atfa-tech__col",children:[a("h4",{class:"atfa-tech__title",text:o.label}),a("ul",{class:"atfa-bars",children:r.map(c=>a("li",{class:"atfa-bars__row",children:[a("span",{class:"atfa-bars__label",text:c.label}),a("div",{class:"atfa-meter",children:[a("div",{class:"atfa-meter__fill",attrs:{style:`width:${l?c.share/l*100:0}%`}})]}),a("span",{class:"atfa-bars__value",text:c.conversion===null?`${c.share}%`:`${c.share}% → ${c.conversion}%`,attrs:c.conversion===null?{title:`${c.submissions} submissions`}:{title:`${c.submissions} submissions from ${c.views} views — ${c.conversion}% converted`}})]}))})]})},i=t.some(o=>e[o.key].some(r=>r.conversion!==null));return this.panel("Devices and browsers",i?"Share of submissions, and → the conversion rate for that class. Aggregate tallies — nothing is stored per visitor.":"Share of submissions. Aggregate tallies — nothing is stored per visitor.",[a("div",{class:"atfa-tech",children:t.map(n)})])}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()}`,"",[this.scrollable(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 ${e.of??t} ${e.unit??"people"} answered this.`})),this.panel(e.label,this.questionSummary(e,t),n)}questionSummary(e,t){const n=[`${e.rate}% answered`];return e.numbers&&n.push(`mean ${e.numbers.mean}`,`median ${e.numbers.median}`),n.push(`${e.answered}/${e.of??t}`),n.join(" · ")}histogram(e){const t=e.numbers;if(!t)return a("div");const n=L(t),i=n.reduce((o,r)=>Math.max(o,r.count),0);return this.scrollable(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})]}))}))}scrollable(e){return a("div",{class:"atfa-scroll",children:[e]})}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)}),i=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 o=await f.demoRemove();x("Demo data removed",`${o.entries} entries and ${o.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,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){x("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.",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 d2a716c..fb827ec 100644 --- a/assets/js/builder.js +++ b/assets/js/builder.js @@ -2520,6 +2520,363 @@ var allTerrainFormsBuilder = function(exports) { } return "#888888"; } + const SUCCESS_STYLE_ICONS = { + plain: "", + simple: "✓", + minimal: "", + card: "🎉", + check: "", + confetti: "🎉", + fireworks: "🎆", + sparkles: "✨", + typewriter: "" + }; + function defaultSuccessScreen() { + return { + style: "simple", + title: "", + icon: "", + accent: "", + intensity: "medium", + showButton: false, + buttonLabel: "" + }; + } + function normalizeSuccessScreen(raw) { + const success = { ...defaultSuccessScreen(), ...raw ?? {} }; + if (!(success.style in SUCCESS_STYLE_ICONS)) { + success.style = "simple"; + } + return success; + } + function reducedMotion() { + return typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches; + } + function renderSuccessScreen(message, raw, onAgain) { + const success = normalizeSuccessScreen(raw); + const root = document.createElement("div"); + root.className = `atf-confirmation atf-success atf-success--${success.style}`; + root.setAttribute("role", "status"); + root.setAttribute("tabindex", "-1"); + if (success.style === "plain") { + root.className = "atf-confirmation"; + root.innerHTML = message; + return root; + } + if (success.accent) { + root.style.setProperty("--atf-accent", success.accent); + } + const inner = document.createElement("div"); + inner.className = "atf-success__inner"; + root.append(inner); + const icon2 = success.icon || SUCCESS_STYLE_ICONS[success.style]; + if (success.style === "check") { + inner.insertAdjacentHTML( + "beforeend", + '' + ); + } else if (icon2) { + const glyph = document.createElement("span"); + glyph.className = "atf-success__icon"; + glyph.setAttribute("aria-hidden", "true"); + glyph.textContent = icon2; + inner.append(glyph); + } + if (success.title) { + const title = document.createElement("h2"); + title.className = "atf-success__title"; + title.textContent = success.title; + inner.append(title); + } + const body = document.createElement("div"); + body.className = "atf-success__message"; + body.innerHTML = message; + inner.append(body); + if (success.style === "typewriter") { + root.setAttribute("aria-label", body.textContent ?? ""); + } + if (success.showButton) { + const again = document.createElement("button"); + again.type = "button"; + again.className = "atf-button atf-button--ghost atf-success__again"; + again.textContent = success.buttonLabel || "Fill it in again"; + again.addEventListener("click", () => onAgain ? onAgain() : window.location.reload()); + inner.append(again); + } + return root; + } + function playSuccessEffects(root, raw) { + const success = normalizeSuccessScreen(raw); + if (reducedMotion()) { + return () => { + }; + } + switch (success.style) { + case "confetti": + return confetti(root, success); + case "fireworks": + return fireworks(success); + case "sparkles": + return sparkles(root, success); + case "typewriter": + return typewriter(root); + default: + return () => { + }; + } + } + function scale(success) { + return { low: 0.5, medium: 1, high: 1.8 }[success.intensity]; + } + function makeCanvas() { + const canvas = document.createElement("canvas"); + canvas.className = "atf-success-canvas"; + canvas.setAttribute("aria-hidden", "true"); + const dpr = Math.min(window.devicePixelRatio || 1, 2); + canvas.width = Math.floor(window.innerWidth * dpr); + canvas.height = Math.floor(window.innerHeight * dpr); + document.body.append(canvas); + const ctx = canvas.getContext("2d"); + ctx?.scale(dpr, dpr); + return { canvas, ctx, stop: () => canvas.remove() }; + } + const CONFETTI_COLORS = ["#f43f5e", "#f59e0b", "#10b981", "#3b82f6", "#8b5cf6", "#ec4899", "#eab308"]; + function confetti(root, success) { + const { ctx, stop } = makeCanvas(); + if (!ctx) { + stop(); + return () => { + }; + } + const colors = success.accent ? [success.accent, ...CONFETTI_COLORS] : CONFETTI_COLORS; + const pieces = []; + const rect = root.getBoundingClientRect(); + const originX = rect.left + rect.width / 2; + const originY = Math.min(rect.top + 40, window.innerHeight - 20); + const make = (x, y, burst) => { + const angle = burst ? Math.PI * (1.15 + 0.7 * Math.random()) : 0; + const speed = burst ? 9 + Math.random() * 8 : 0; + return { + x, + y, + vx: burst ? Math.cos(angle) * speed * (Math.random() < 0.5 ? 1 : -1) : (Math.random() - 0.5) * 1.5, + vy: burst ? Math.sin(angle) * speed : 1 + Math.random() * 2, + w: 6 + Math.random() * 5, + h: 8 + Math.random() * 7, + angle: Math.random() * Math.PI, + spin: (Math.random() - 0.5) * 0.3, + color: colors[Math.floor(Math.random() * colors.length)], + wobble: Math.random() * Math.PI * 2 + }; + }; + const burstCount = Math.round(90 * scale(success)); + for (let i = 0; i < burstCount; i++) { + pieces.push(make(originX, originY, true)); + } + const rainCount = Math.round(70 * scale(success)); + let rained = 0; + const rain = window.setInterval(() => { + if (rained >= rainCount) { + window.clearInterval(rain); + return; + } + pieces.push(make(Math.random() * window.innerWidth, -20, false)); + rained++; + }, 2e3 / rainCount); + let frame = 0; + const started = performance.now(); + const tick = (now) => { + ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); + let alive = false; + for (const piece of pieces) { + piece.vy += 0.16; + piece.vx *= 0.99; + piece.vy *= 0.985; + piece.wobble += 0.1; + piece.x += piece.vx + Math.sin(piece.wobble) * 0.8; + piece.y += piece.vy; + piece.angle += piece.spin; + if (piece.y < window.innerHeight + 30) { + alive = true; + } + ctx.save(); + ctx.translate(piece.x, piece.y); + ctx.rotate(piece.angle); + ctx.scale(1, 0.4 + 0.6 * Math.abs(Math.cos(piece.wobble))); + ctx.fillStyle = piece.color; + ctx.fillRect(-piece.w / 2, -piece.h / 2, piece.w, piece.h); + ctx.restore(); + } + if (alive && now - started < 7e3) { + frame = window.requestAnimationFrame(tick); + } else { + stop(); + } + }; + frame = window.requestAnimationFrame(tick); + return () => { + window.clearInterval(rain); + window.cancelAnimationFrame(frame); + stop(); + }; + } + function fireworks(success) { + const { ctx, stop } = makeCanvas(); + if (!ctx) { + stop(); + return () => { + }; + } + const rockets = []; + const sparks = []; + const total = Math.round(6 * scale(success)) + 2; + let launched = 0; + const launch = () => { + rockets.push({ + x: window.innerWidth * (0.15 + 0.7 * Math.random()), + y: window.innerHeight, + vy: -(9 + Math.random() * 4), + targetY: window.innerHeight * (0.15 + 0.3 * Math.random()), + hue: Math.floor(Math.random() * 360) + }); + launched++; + }; + launch(); + const launcher = window.setInterval(() => { + if (launched >= total) { + window.clearInterval(launcher); + return; + } + launch(); + }, 3500 / total); + const explode = (rocket) => { + const count = Math.round(70 * scale(success)); + for (let i = 0; i < count; i++) { + const angle = Math.PI * 2 * i / count + Math.random() * 0.1; + const speed = 2 + Math.random() * 4.5; + sparks.push({ + x: rocket.x, + y: rocket.y, + vx: Math.cos(angle) * speed, + vy: Math.sin(angle) * speed, + life: 1, + decay: 0.012 + Math.random() * 0.014, + hue: rocket.hue + Math.floor(Math.random() * 40) - 20 + }); + } + }; + let frame = 0; + const started = performance.now(); + const tick = (now) => { + ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); + for (let i = rockets.length - 1; i >= 0; i--) { + const rocket = rockets[i]; + rocket.y += rocket.vy; + rocket.vy += 0.08; + ctx.fillStyle = `hsl(${rocket.hue} 90% 65%)`; + ctx.fillRect(rocket.x - 1.5, rocket.y, 3, 10); + if (rocket.y <= rocket.targetY || rocket.vy >= -1) { + explode(rocket); + rockets.splice(i, 1); + } + } + for (let i = sparks.length - 1; i >= 0; i--) { + const spark = sparks[i]; + spark.x += spark.vx; + spark.y += spark.vy; + spark.vy += 0.045; + spark.vx *= 0.985; + spark.vy *= 0.985; + spark.life -= spark.decay; + if (spark.life <= 0) { + sparks.splice(i, 1); + continue; + } + const flicker = spark.life * (0.7 + 0.3 * Math.random()); + ctx.globalAlpha = Math.max(0, flicker); + ctx.fillStyle = `hsl(${spark.hue} 95% ${55 + 25 * spark.life}%)`; + ctx.beginPath(); + ctx.arc(spark.x, spark.y, 1.1 + 1.6 * spark.life, 0, Math.PI * 2); + ctx.fill(); + } + ctx.globalAlpha = 1; + const done = launched >= total && rockets.length === 0 && sparks.length === 0; + if (!done && now - started < 9e3) { + frame = window.requestAnimationFrame(tick); + } else { + stop(); + } + }; + frame = window.requestAnimationFrame(tick); + return () => { + window.clearInterval(launcher); + window.cancelAnimationFrame(frame); + stop(); + }; + } + function sparkles(root, success) { + const glyph = success.icon || SUCCESS_STYLE_ICONS.sparkles; + const count = Math.round(22 * scale(success)); + const spawned = []; + let made = 0; + const spawn = () => { + const spark = document.createElement("span"); + spark.className = "atf-success__spark"; + spark.setAttribute("aria-hidden", "true"); + spark.textContent = glyph; + spark.style.insetInlineStart = `${4 + Math.random() * 92}%`; + spark.style.animationDuration = `${2.6 + Math.random() * 1.8}s`; + spark.style.animationDelay = `${Math.random() * 0.3}s`; + spark.style.fontSize = `${14 + Math.random() * 14}px`; + spark.addEventListener("animationend", () => spark.remove()); + root.append(spark); + spawned.push(spark); + made++; + }; + spawn(); + const spawner = window.setInterval(() => { + if (made >= count) { + window.clearInterval(spawner); + return; + } + spawn(); + }, 2800 / count); + return () => { + window.clearInterval(spawner); + spawned.forEach((spark) => spark.remove()); + }; + } + function typewriter(root) { + const body = root.querySelector(".atf-success__message"); + if (!body) { + return () => { + }; + } + const html = body.innerHTML; + const text = body.textContent ?? ""; + if (!text) { + return () => { + }; + } + body.textContent = ""; + body.classList.add("is-typing"); + const step = Math.min(45, 3800 / text.length); + let at = 0; + const typer = window.setInterval(() => { + at++; + body.textContent = text.slice(0, at); + if (at >= text.length) { + window.clearInterval(typer); + body.classList.remove("is-typing"); + body.innerHTML = html; + } + }, step); + return () => { + window.clearInterval(typer); + body.classList.remove("is-typing"); + body.innerHTML = html; + }; + } const FUNCTIONS = { min: -1, max: -1, @@ -7208,6 +7565,25 @@ var allTerrainFormsBuilder = function(exports) { ) ] }), + el("section", { + children: [ + el("h3", { text: "Analytics" }), + checkbox( + "Count views and starts", + settings.analytics.enabled, + (value) => set("analytics.enabled", value) + ), + checkbox( + "Tally device, browser and system", + settings.analytics.tech, + (value) => set("analytics.tech", value) + ), + el("p", { + class: "atfb-hint", + text: "Counters, never people: no cookie, no fingerprint, no per-visitor record — the tallies keep coarse classes like “phone” and “Chrome”, not the visitor’s user-agent string. Nothing here needs a consent banner. Turning the first switch off stops view and start counting entirely." + }) + ] + }), el("section", { children: [ el("h3", { text: "Save and continue later" }), @@ -7509,18 +7885,23 @@ var allTerrainFormsBuilder = function(exports) { */ confirmationDetail(confirmation) { if ("message" === confirmation.type) { - return row( - "Message", - this.taggableArea( - confirmation.message, - (value) => { - confirmation.message = value; - this.markDirty(); - }, - 5 - ), - "Insert an answer to greet them by name, or show back what they sent." - ); + return el("div", { + children: [ + row( + "Message", + this.taggableArea( + confirmation.message, + (value) => { + confirmation.message = value; + this.markDirty(); + }, + 5 + ), + "Insert an answer to greet them by name, or show back what they sent." + ), + this.successDesigner(confirmation) + ] + }); } const query2 = row( "Extra query parameters", @@ -7577,6 +7958,218 @@ var allTerrainFormsBuilder = function(exports) { "They are sent to this page after submitting. Its own content is shown, not the form’s message." ); } + /** + * The success screen designer: what the thank-you moment looks like. + * + * A gallery of styles rather than a dropdown, because the styles are + * *looks* and a look chosen from a list of words is a look chosen blind. + * Picking one plays the real screen immediately — the renderer previewing + * here is the same code the visitor's browser runs, so what the author + * sees is what ships. + */ + successDesigner(confirmation) { + confirmation.success = normalizeSuccessScreen(confirmation.success); + const success = confirmation.success; + const holder = el("div", { class: "atfb-success" }); + const styles = [ + { key: "plain", label: "Plain", glyph: "¶", blurb: "Just the message." }, + { key: "simple", label: "Simple", glyph: "✓", blurb: "Check mark and a gentle fade." }, + { key: "minimal", label: "Minimalistic", glyph: "—", blurb: "Quiet type, generous space." }, + { key: "card", label: "Card", glyph: "🎫", blurb: "An elevated card that pops in." }, + { key: "check", label: "Check mark", glyph: "✔", blurb: "A big check draws itself." }, + { key: "confetti", label: "Confetti", glyph: "🎉", blurb: "Paper rains over the page." }, + { key: "fireworks", label: "Fireworks", glyph: "🎆", blurb: "The full night-sky show." }, + { key: "sparkles", label: "Sparkles", glyph: "✨", blurb: "Your emoji floats up around it." }, + { key: "typewriter", label: "Typewriter", glyph: "⌨", blurb: "Types itself out, letter by letter." } + ]; + const paint = () => { + const gallery = el("div", { + class: "atfb-success__styles", + attrs: { role: "radiogroup", "aria-label": "Success screen style" }, + children: styles.map( + (style) => el("button", { + class: `atfb-success__style${success.style === style.key ? " is-selected" : ""}`, + type: "button", + attrs: { + role: "radio", + "aria-checked": success.style === style.key ? "true" : "false", + title: style.blurb + }, + children: [ + el("span", { class: "atfb-success__style-glyph", text: style.glyph }), + el("span", { class: "atfb-success__style-label", text: style.label }) + ], + on: { + click: () => { + success.style = style.key; + this.markDirty(); + paint(); + if ("plain" !== style.key) { + this.previewSuccessScreen(confirmation); + } + } + } + }) + ) + }); + const controls = []; + if ("plain" !== success.style) { + controls.push( + row( + "Heading", + this.taggableInput( + success.title, + (value) => { + success.title = value; + this.markDirty(); + }, + "Thank you, {field:name}!" + ), + "Shown above the message. Leave empty for none." + ) + ); + if (["simple", "card", "confetti", "fireworks", "sparkles"].includes(success.style)) { + controls.push( + row( + "Emoji", + textInput(success.icon, (value) => { + success.icon = value; + this.markDirty(); + }, SUCCESS_STYLE_ICONS[success.style] || "🎉"), + "sparkles" === success.style ? "Also the particle that floats up — try 🎈 or ❤️." : "The badge above the heading." + ) + ); + } + controls.push(this.successAccentRow(success)); + if (["confetti", "fireworks", "sparkles"].includes(success.style)) { + controls.push( + row( + "Intensity", + select( + success.intensity, + [ + { value: "low", label: "Calm" }, + { value: "medium", label: "Festive" }, + { value: "high", label: "Over the top" } + ], + (value) => { + success.intensity = value; + this.markDirty(); + } + ) + ) + ); + } + controls.push( + checkbox("Offer to fill it in again", success.showButton, (value) => { + success.showButton = value; + this.markDirty(); + paint(); + }) + ); + if (success.showButton) { + controls.push( + row( + "Button label", + textInput(success.buttonLabel, (value) => { + success.buttonLabel = value; + this.markDirty(); + }, "Fill it in again") + ) + ); + } + } + holder.replaceChildren( + el("div", { + class: "atfb-success__head", + children: [ + el("h4", { text: "Success screen" }), + button("Preview", () => this.previewSuccessScreen(confirmation), "ghost", "controls-play") + ] + }), + gallery, + ...controls + ); + }; + paint(); + return holder; + } + /** The accent picker: a colour, or the theme's own accent by default. */ + successAccentRow(success) { + const input = el("input", { + class: "atfb-success__accent", + attrs: { type: "color", "aria-label": "Accent colour" } + }); + input.value = success.accent || "#3858e9"; + const reset = button("Use the theme accent", () => { + success.accent = ""; + input.value = "#3858e9"; + reset.style.display = "none"; + this.markDirty(); + }, "ghost"); + reset.style.display = success.accent ? "" : "none"; + input.addEventListener("input", () => { + success.accent = input.value; + reset.style.display = ""; + this.markDirty(); + }); + return row( + "Accent", + el("div", { class: "atfb-success__accent-row", children: [input, reset] }), + "Recolours the screen; the theme decides when left alone." + ); + } + /** + * Plays the success screen over the builder, exactly as it will ship. + * + * The stage wears the canvas's own preview classes so the form's real theme + * tokens reach it, and the screen inside is built by the front-end renderer + * itself. Escape, the backdrop or the close button put the builder back. + */ + previewSuccessScreen(confirmation) { + const message = confirmation.message || "Thank you. Your submission has been received."; + let cleanup = () => { + }; + const overlay = el("div", { + class: "atfb-success-preview", + attrs: { role: "dialog", "aria-label": "Success screen preview", "aria-modal": "true" } + }); + const dismiss = () => { + cleanup(); + overlay.remove(); + document.removeEventListener("keydown", onKey); + }; + const onKey = (event) => { + if ("Escape" === event.key) { + event.stopPropagation(); + dismiss(); + } + }; + const screen = renderSuccessScreen(message, confirmation.success, dismiss); + const stage = el("div", { class: "atfb-success-preview__stage atfb-preview atf-form", children: [screen] }); + const play = () => { + cleanup(); + cleanup = playSuccessEffects(screen, confirmation.success); + }; + overlay.append( + stage, + el("div", { + class: "atfb-success-preview__bar", + children: [ + button("Replay", play, "secondary", "controls-repeat"), + button("Close", dismiss, "primary") + ] + }) + ); + overlay.addEventListener("click", (event) => { + if (event.target === overlay) { + dismiss(); + } + }); + document.addEventListener("keydown", onKey); + this.root.append(overlay); + window.requestAnimationFrame(play); + } /** The confirmation editor. */ renderConfirmationsPane() { const confirmations = this.schema.confirmations; @@ -7655,6 +8248,7 @@ var allTerrainFormsBuilder = function(exports) { url: "", pageId: 0, query: "", + success: defaultSuccessScreen(), logic: { enabled: false, action: "show", match: "all", rules: [] } }); this.markDirty(); diff --git a/assets/js/builder.min.js b/assets/js/builder.min.js index b4250ef..66d78ba 100644 --- a/assets/js/builder.min.js +++ b/assets/js/builder.min.js @@ -1,3 +1,3 @@ -var allTerrainFormsBuilder=function(ee){"use strict";class Nt{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,n=s.clientX,i=s.clientY;let o=!1,l=!1,c=null,d=null,h=0,g=0;const v=()=>{document.removeEventListener("pointermove",x),document.removeEventListener("pointerup",F),document.removeEventListener("pointercancel",H),document.removeEventListener("keydown",Y),window.removeEventListener("blur",H),c?.remove(),c=null,t.source.classList.remove("atf-is-dragging"),d?.onLeave?.(p),d=null,this.active=null,this.lastEndMs=Date.now()},p={payload:t,isFinished:()=>l,cancel:(A="caller")=>{l||(l=!0,v(),e.onCancel?.(A))}},_=A=>{o=!0,t.source.classList.add("atf-is-dragging");const C=t.source.getBoundingClientRect();h=t.ghost?.offsetX??n-C.left,g=t.ghost?.offsetY??i-C.top,c=t.ghost?.element??t.source.cloneNode(!0),c.classList.add("atf-drag-ghost"),c.style.width=`${C.width}px`,document.body.appendChild(c),y(A)},y=A=>{c&&(c.style.transform=`translate3d(${A.clientX-h}px, ${A.clientY-g}px, 0)`)},x=A=>{if(l)return;if(!o){if(Math.hypot(A.clientX-n,A.clientY-i)<4)return;_(A)}y(A);const C=this.hitTest(A.clientX,A.clientY);C!==d&&(d?.onLeave?.(p),d=C,d?.onEnter?.(p))},F=A=>{if(l)return;if(!o){l=!0,v(),e.onClickOnly?.();return}const C=d;if(l=!0,v(),C&&C.accept(t)){e.onCommit?.(C),C.onDrop(p,{clientX:A.clientX,clientY:A.clientY});return}e.onCancel?.(C?"rejected":"no-target")},H=()=>p.cancel("pointercancel"),Y=A=>{A.key==="Escape"&&p.cancel("escape")};return document.addEventListener("pointermove",x),document.addEventListener("pointerup",F),document.addEventListener("pointercancel",H),document.addEventListener("keydown",Y),window.addEventListener("blur",H),this.active=p,p}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=Rt(i.element);if(l>n){s=i,n=l;continue}l===n&&s&&Pt(i.element,s.element)&&(s=i)}return s}}function Rt(a){let e=0,t=a;for(;t;)e++,t=t.parentElement;return e}function Pt(a,e){return(e.compareDocumentPosition(a)&Node.DOCUMENT_POSITION_FOLLOWING)!==0}function xe(){return window.wp?.os??null}let _e=null;function K(){const a=xe();return a?.dragManager?a.dragManager:(_e||(_e=new Nt),_e)}function Bt(a){const e=n=>{const i=n.detail?.payload;return i&&a.includes(i.type)?i.source:null},t=n=>e(n)?.classList.add("atf-is-dragging"),s=n=>e(n)?.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 ke(a,e,t,s,n){const i=e.getBoundingClientRect();return n&&(n.style.width=`${Math.round(i.width)}px`,n.style.maxWidth=`${Math.round(i.width)}px`,n.style.boxSizing="border-box"),{type:a,source:e,data:t,ghost:{element:n,offsetX:s.clientX-i.left,offsetY:s.clientY-i.top,hint:{neutral:"",accept:"",reject:"",hidden:!0}}}}function Se(a,e,t,s){const n=Array.from(a.querySelectorAll(e)).filter(i=>i!==s&&!i.classList.contains("atf-drag-ghost"));for(let i=0;in.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 le(l,o.status,c)}if(o.status!==204)return await o.json()}const U=a=>Le(a);async function jt(a){if(!j?.wpRestUrl)throw new le("AllTerrain Forms is not configured on this page.",0);const e=j.nonce?{"X-WP-Nonce":j.nonce}:{},t=xe(),s=Ye(j.wpRestUrl,a),n=t?.fetch?await t.fetch(s,{credentials:"same-origin",headers:e},{source:"allterrain-forms"}):await fetch(s,{credentials:"same-origin",headers:e});if(!n.ok)throw new le(`Request failed with status ${n.status}.`,n.status);return await n.json()}function Wt(a){if(!a||!a.includes("&"))return a;const e=document.createElement("textarea");return e.innerHTML=a,e.value}const X=(a,e)=>Le(a,{method:"POST",body:JSON.stringify(e)}),ce=a=>Le(a,{method:"DELETE"});function Ge(a){const e=new URLSearchParams;for(const[s,n]of Object.entries(a))n===void 0||n===""||n===!1||e.set(s,String(n));const t=e.toString();return t?`?${t}`:""}function de(a){const e=a?.schema?.settings;return e&&(!e.themeOverrides||Array.isArray(e.themeOverrides))&&(e.themeOverrides={...e.themeOverrides}),a}const I={config:()=>U("/config"),listForms:()=>U("/forms"),listArchivedForms:()=>U("/forms?archived=1"),archiveForm:a=>X(`/forms/${a}/archive`,{}),unarchiveForm:a=>X(`/forms/${a}/unarchive`,{}),getForm:a=>U(`/forms/${a}`).then(de),createForm:a=>X("/forms",a).then(de),updateForm:(a,e)=>X(`/forms/${a}`,e).then(de),duplicateForm:a=>X(`/forms/${a}/duplicate`,{}).then(de),deleteForm:a=>ce(`/forms/${a}`),preview:(a,e)=>X(`/forms/${a}/preview`,e),pages:()=>jt("wp/v2/pages?per_page=100&status=publish&orderby=title&order=asc&_fields=id,title").then(a=>a.map(e=>({id:e.id,title:Wt(e.title?.rendered??"")||`#${e.id}`}))),mergeTags:a=>U(`/forms/${a}/merge-tags`).then(e=>e.groups),analytics:(a,e="")=>U(`/forms/${a}/analytics${e?`?dimension=${encodeURIComponent(e)}`:""}`),demoStatus:()=>U("/demo"),demoSeed:a=>X("/demo",a?{count:a}:{}),demoRemove:()=>ce("/demo"),listEntries:a=>U(`/entries${Ge(a)}`),getEntry:a=>U(`/entries/${a}`),updateEntry:(a,e)=>X(`/entries/${a}`,e),deleteEntry:a=>ce(`/entries/${a}`),exportEntries:a=>U(`/entries/export${Ge(a)}`),listThemes:()=>U("/themes"),saveTheme:a=>X("/themes",a),deleteTheme:a=>ce(`/themes/${a}`)};function r(a,e={}){const t=document.createElement(a);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,n]of Object.entries(e.attrs??{}))n===void 0||n===!1||t.setAttribute(s,n===!0?"":String(n));Object.assign(t.style,e.style??{});for(const[s,n]of Object.entries(e.on??{}))t.addEventListener(s,n);for(const s of e.children??[])s==null||s===!1||t.append(s);return t}function P(a){a.replaceChildren()}function W(a){return r("span",{class:`dashicons ${a.startsWith("dashicons-")?a:`dashicons-${a}`}`,attrs:{"aria-hidden":"true"}})}function m(a,e,t){const s=e.matches("input, select, textarea, button")?e:e.querySelector("input, select, textarea");if(s){const n=s.id||`atf-c-${Math.random().toString(36).slice(2,9)}`;return s.id=n,r("div",{class:"atfb-row",children:[r("label",{class:"atfb-row__label",text:a,attrs:{for:n}}),e,t?r("p",{class:"atfb-row__hint",text:t}):null]})}return r("div",{class:"atfb-row",children:[r("span",{class:"atfb-row__label",text:a}),e,t?r("p",{class:"atfb-row__hint",text:t}):null]})}function L(a,e,t=""){return r("input",{class:"atfb-input",type:"text",value:a,placeholder:t,on:{input:s=>e(s.target.value)}})}function G(a,e){if(z("os-number-field")){const t=document.createElement("os-number-field");return t.setAttribute("value",a),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:a,on:{input:t=>e(t.target.value)}})}function he(a,e,t=4){const s=r("textarea",{class:"atfb-input atfb-input--area",attrs:{rows:t},on:{input:n=>e(n.target.value)}});return s.value=a,s}function M(a,e,t){if(z("os-select")&&z("os-option")){const s=document.createElement("os-select");s.setAttribute("value",a),s.classList.add("atfb-field");for(const n of e){const i=document.createElement("os-option");i.setAttribute("value",n.value),i.textContent=n.label,s.append(i)}return s.addEventListener("os-pick",n=>{t(String(n.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===a}}))})}function D(a,e,t){if(z("os-checkbox-label")){const n=document.createElement("os-checkbox-label");return n.setAttribute("label",a),n.classList.add("atfb-check"),e&&n.setAttribute("checked",""),n.addEventListener("os-checkbox-change",i=>{t(!!i.detail?.checked)}),n}const s=r("input",{type:"checkbox",on:{change:n=>t(n.target.checked)}});return s.checked=e,r("label",{class:"atfb-check",children:[s,r("span",{text:a})]})}function E(a,e,t="secondary",s){const n=[s?W(s):null,r("span",{text:a})];if(z("os-button")){const i=document.createElement("os-button");i.setAttribute("variant",t),i.setAttribute("type","button"),i.classList.add("atfb-button",`atfb-button--${t}`),i.addEventListener("click",e);for(const o of n)o&&i.append(o);return i}return r("button",{class:`atfb-button atfb-button--${t}`,type:"button",on:{click:e},children:n})}function z(a){return typeof customElements<"u"&&!!customElements.get(a)}const Ht=["os-button","os-checkbox-label","os-number-field","os-select","os-option","os-segmented","os-segment","os-color-field","os-empty-state"];let ue=null;function Ut(){if(ue)return ue;const a=window.wp?.os;return ue=a?.loadComponents?a.loadComponents(Ht).catch(()=>{}):Promise.resolve(),ue}async function pe(a,e=""){const t=window.wp?.os;return t?.confirm?t.confirm({title:e,message:a,danger:!0}):window.confirm(a)}function R(a,e="",t="info"){const s=window.wp?.os;if(s?.notify){s.notify({title:a,body:e,type:t});return}console.info(`[AllTerrain Forms] ${a}${e?`: ${e}`:""}`)}function zt(a){let e=0,t;return(...s)=>{t=s,!e&&(e=window.requestAnimationFrame(()=>{e=0,a(...t)}))}}function Ee(a,e){let t=0;return(...s)=>{window.clearTimeout(t),t=window.setTimeout(()=>a(...s),e)}}function Yt(a){try{return window.localStorage.getItem(a)??""}catch{return""}}function Gt(a,e){try{window.localStorage.setItem(a,e)}catch{}}function Ze(a){const e=a.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 Ke(){const a=[window];try{window.parent&&window.parent!==window&&a.push(window.parent)}catch{}return a}function Zt(a){const e=a.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 Kt=4e3,Xt=100;function Vt(a,e){const t=Date.now()+Kt,s=()=>{const n=Zt(a);if(n&&n.id!==e){typeof n.close=="function"?n.close():a.wp?.os?.windowManager?.remove?.(n.id);return}!n&&Date.now()0?{form:t}:void 0;t>0&&ts(t);for(const n of Ke())if(n.wp?.os?.openWindow?.(e,{source:"handoff",params:s})){window.setTimeout(()=>Vt(n,e),0);return}}function Qt(){document.addEventListener("click",a=>{const e=a.target?.closest?.("[data-atf-open-window]");if(!e)return;a.preventDefault();const t=e.getAttribute("data-atf-open-window")??"";for(const s of Ke())if(s.wp?.os?.openWindow?.(t,{source:"handoff"}))return})}const es="allterrain-forms/requested-form";function ts(a){try{window.sessionStorage.setItem(es,String(a))}catch{}}function Xe(a){return`allterrain-forms/requested-form-${a}`}function ss(a){try{const e=window.sessionStorage.getItem(Xe(a));return window.sessionStorage.removeItem(Xe(a)),Number(e)||0}catch{return 0}}const $e={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"},Ae=["empty","not_empty"];function Ve(a,e){const t=a.find(s=>s.id===e);return t?t.label||"an untitled question":null}function Je(a,e,t){return a.find(i=>i.id===e)?.choices?.find(i=>i.value===t)?.label||t}function ns(a,e){const t=$e[a.operator]??String(a.operator);if(Ae.includes(a.operator))return t;const s=Je(e,a.field,a.value);return`${a.operator==="is"?"":`${t} `}${s!==""?s:"(nothing)"}`}function Qe(a,e,t=0){const s=Ve(e,a.field),n=$e[a.operator]??String(a.operator);if(s===null)return[{kind:"field",text:"a question that no longer exists",fieldId:a.field,missing:!0}];const i=[{kind:"field",text:s,fieldId:a.field,missing:!1},{kind:"operator",text:n,operator:a.operator,ruleIndex:t}];if(Ae.includes(a.operator))return i;const o=Je(e,a.field,a.value);return i.push({kind:"value",text:o!==""?o:"(nothing)",raw:a.value,sourceId:a.field,ruleIndex:t}),i}function as(a,e){const t=a.logic;if(!t?.enabled||!t.rules.length)return[];const s=[{kind:"verb",text:t.action==="hide"?"Hidden when":"Shown when"}];return t.rules.forEach((n,i)=>{i>0&&s.push({kind:"join",text:t.match==="all"?"and":"or"}),s.push(...Qe(n,e,i))}),s}function et(a){return a.map(e=>e.text).join(" ")}function is(a,e){return et(Qe(a,e))}function fe(a){const e=[];for(const t of a){const s=t.logic;if(s?.enabled)for(const n of s.rules)!n.field||n.field===t.id||e.push({from:n.field,to:t.id,label:is(n,a),short:ns(n,a),action:s.action,broken:Ve(a,n.field)===null})}return e}function rs(a){const e=new Map,t=new Set;for(const s of fe(a)){const n=`${s.from}->${s.to}`;s.broken||t.has(n)||(t.add(n),e.set(s.from,(e.get(s.from)??0)+1))}return e}const tt=10,st=22,ne="http://www.w3.org/2000/svg";class os{constructor(e){this.edges=[],this.teardowns=[],this.frame=0,this.host=e,this.svg=document.createElementNS(ne,"svg"),this.svg.setAttribute("class","atfb-logicmap"),this.svg.setAttribute("aria-hidden","true"),this.svg.setAttribute("focusable","false"),e.append(this.svg);const t=()=>this.schedule();window.addEventListener("resize",t),this.teardowns.push(()=>window.removeEventListener("resize",t));const s=e.closest(".atfb__canvas")??e;if(s.addEventListener("scroll",t,{passive:!0}),this.teardowns.push(()=>s.removeEventListener("scroll",t)),typeof ResizeObserver<"u"){const n=new ResizeObserver(t);n.observe(e),this.teardowns.push(()=>n.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 n=this.cardRect(s.from),i=this.cardRect(s.to);if(!n||!i)continue;const o=t.get(s.from)??0;t.set(s.from,o+1),this.drawEdge(s,n,i,e,o)}}cardRect(e){const t=this.host.querySelector(`[data-atfb-card="${CSS.escape(e)}"]`);return t?t.getBoundingClientRect():null}drawEdge(e,t,s,n,i){const o=t.right-n.left,l=t.top+t.height/2-n.top,c=s.right-n.left,d=s.top+s.height/2-n.top,h=Math.max(0,n.width-tt-Math.max(o,c)),g=22+i*11+Math.min(22,Math.abs(d-l)/8),v=Math.max(12,Math.min(g,h*.4)),p=document.createElementNS(ne,"path");p.setAttribute("d",`M ${o} ${l} C ${o+v} ${l}, ${c+v} ${d}, ${c} ${d}`),p.setAttribute("class",`atfb-logicmap__path is-${e.action}${e.broken?" is-broken":""}`),p.dataset.from=e.from,p.dataset.to=e.to;const _=document.createElementNS(ne,"title");_.textContent=e.label,p.append(_);const y=document.createElementNS(ne,"circle");y.setAttribute("cx",String(c)),y.setAttribute("cy",String(d)),y.setAttribute("r","3.5"),y.setAttribute("class",`atfb-logicmap__dot is-${e.action}${e.broken?" is-broken":""}`),y.dataset.from=e.from,y.dataset.to=e.to,this.svg.append(p,y);const x=document.createElementNS(ne,"text");x.setAttribute("x",String(n.width-tt)),x.setAttribute("y",String(d+3)),x.setAttribute("text-anchor","end"),x.setAttribute("class","atfb-logicmap__label"),x.dataset.from=e.from,x.dataset.to=e.to,x.textContent=e.short.length>st?`${e.short.slice(0,st-1)}…`:e.short;const F=document.createElementNS(ne,"title");F.textContent=e.label,x.append(F),this.svg.append(x)}}const ls={text:"text",email:"text",url:"text",tel:"text",number:"text",password:"text",date:"text",time:"text",datetime:"text",date_range:"composite",textarea:"textarea",select:"select",country:"select",multiselect:"options",radio:"options",checkboxes:"options",image_choice:"options",quiz:"options",switch:"toggle",consent:"toggle",file:"file",range:"range",rating:"summary",scale:"summary",likert:"summary",signature:"summary",repeater:"repeater",color:"text",name:"composite",address:"composite",total:"text",hidden:"static",heading:"static",html:"static",divider:"static",spacer:"static",page_break:"static"};function cs(a){return ls[a]??"text"}function nt(a,e){const t=/^choice:(\d+):(label|value)$/.exec(e);return String(t?a.choices?.[Number(t[1])]?.[t[2]]??"":a[e]??"")}function V(a){const{value:e,onInput:t,onCommit:s}=a,n=r("span",{class:`${a.class} atfb-editable`,text:e,attrs:{contenteditable:"plaintext-only",role:"textbox",spellcheck:"false","data-placeholder":a.placeholder}});return a.bind&&(n.dataset.atfbBind=a.bind),n.addEventListener("pointerdown",i=>i.stopPropagation()),n.addEventListener("input",()=>t(n.textContent??"")),n.addEventListener("keydown",i=>{i.key==="Enter"&&(i.preventDefault(),n.blur()),i.key==="Escape"&&(n.textContent=e,n.blur()),i.stopPropagation()}),n.addEventListener("blur",()=>s?.()),n}function ds(a){const e=r("input",{class:"atf-choice__input",type:a==="checkboxes"||a==="multiselect"?"checkbox":"radio"});return e.disabled=!0,e}function at(a,e,t){const s=cs(a.type),n=V({value:a.label,placeholder:"Write the question…",class:"atf-label",bind:"label",onInput:o=>t.edit(l=>{l.label=o}),onCommit:()=>t.restructure(()=>{})}),i=[s==="static"||s==="toggle"?null:n,us(a,e,s,t),hs(a,e,t)];return r("div",{class:"atfb-preview atf-form",children:[r("div",{class:`atf-field atf-field--${a.type}`,children:i})]})}function hs(a,e,t){if(!e?.supports.includes("hint"))return null;const s=V({value:a.hint??"",placeholder:"Add a hint…",class:"atf-hint",bind:"hint",onInput:n=>t.edit(i=>{i.hint=n})});return s.setAttribute("aria-label","Hint"),r("p",{class:"atfb-preview__hint",children:[s]})}function us(a,e,t,s){switch(t){case"text":return Ce(a,"atf-input",s);case"textarea":return Ce(a,"atf-input atf-textarea",s,!0);case"select":return r("div",{class:"atfb-preview__select",children:[Ce(a,"atf-input atf-select",s)]});case"options":return bs(a,s);case"toggle":return r("div",{class:a.type==="switch"?"atf-toggle atf-toggle--switch":"atf-toggle",children:[(()=>{const n=r("input",{class:"atf-toggle__input",type:"checkbox"});return n.disabled=!0,n})(),V({value:a.label||"",placeholder:a.type==="consent"?"What are they agreeing to?…":"What does this turn on?…",class:"atf-toggle__label",bind:"label",onInput:n=>s.edit(i=>{i.label=n}),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 gs(a,e,s);case"repeater":return ps(a,s);default:return r("div",{class:"atfb-preview__stack",children:[r("p",{class:"atfb-preview__summary",text:`${e?.label??a.type} — set this up in the panel on the right.`})]})}}function ps(a,e){const t=a.fields??[],s=r("div",{class:"atfb-repeater__zone",attrs:{"data-atfb-repeater-zone":a.id}});return t.forEach(n=>s.append(fs(a,n,e))),t.length||s.append(r("p",{class:"atfb-repeater__empty",text:"Drag fields from the palette in here — the visitor gets a fresh copy of each with every row they add."})),r("div",{class:"atfb-repeater",attrs:{"data-atfb-repeater":a.id},children:[r("div",{class:"atf-repeater__row atfb-repeater__frame",children:[r("div",{class:"atf-repeater__row-head",children:[r("span",{class:"atf-repeater__title atfb-repeater__title",children:[V({value:String(a.itemLabel??""),placeholder:"Row",class:"atfb-repeater__item-label",bind:"itemLabel",onInput:n=>e.edit(i=>{i.itemLabel=n})}),r("span",{class:"atfb-repeater__ordinal",text:"1",attrs:{"aria-hidden":"true"}})]})]}),s]}),ms(a,e)]})}function fs(a,e,t){const s=e.id,n=l=>c=>{const d=(c.fields??[]).find(h=>h.id===s);d&&l(d)},i={edit:l=>t.edit(n(l)),restructure:l=>t.restructure(n(l)),types:t.types,selectedId:t.selectedId},o=t.types?.(e.type);return r("div",{class:`atfb-subcard${t.selectedId===s?" is-selected":""}`,attrs:{"data-atfb-subfield":s,"data-atfb-parent":a.id,tabindex:"0",role:"button","aria-label":`${e.label||o?.label||e.type}, inside ${a.label||"the repeater"}`},children:[r("div",{class:"atfb-subcard__head",children:[r("span",{class:"atfb-subcard__type",text:o?.label??e.type}),e.required?r("span",{class:"atfb-subcard__required",text:"Required"}):null,r("button",{class:"atfb-preview__remove",type:"button",title:"Remove this field from the repeater",attrs:{"aria-label":`Remove ${e.label||o?.label||"this field"}`},on:{pointerdown:l=>l.stopPropagation(),click:l=>{l.stopPropagation(),t.restructure(c=>{const d=c.fields??[],h=d.findIndex(g=>g.id===s);h>=0&&d.splice(h,1)})}},children:[r("span",{text:"×"})]})]}),at(e,o,i)]})}function Ce(a,e,t,s=!1){const n=V({value:a.placeholder??"",placeholder:a.type==="select"||a.type==="country"?"Choose…":"Placeholder…",class:`${e} atfb-preview__box${s?" atfb-preview__box--tall":""}`,bind:"placeholder",onInput:i=>t.edit(o=>{o.placeholder=i})});return n.setAttribute("aria-label","Placeholder"),n}function Te(a,e,t,s,n,i){return r("span",{class:`atf-button ${t} atfb-preview__button`,children:[V({value:a,placeholder:e,class:"atfb-preview__button-text",bind:s,onInput:o=>n.edit(l=>i(l,o))})]})}function ms(a,e){return Te(String(a.addLabel??""),"Add another","atf-button--ghost atfb-repeater__add","addLabel",e,(t,s)=>{t.addLabel=s})}function bs(a,e){const t=r("div",{class:"atf-choices__list"});return(a.choices??[]).forEach((s,n)=>{t.append(r("div",{class:"atf-choice atfb-preview__option",children:[ds(a.type),V({value:s.label,placeholder:"Option…",class:"atf-choice__label",bind:`choice:${n}:label`,onInput:i=>{e.edit(o=>{const l=o.choices?.[n];if(!l)return;const c=!l.value||l.value===l.label;l.label=i,c&&(l.value=i)})}}),r("button",{class:"atfb-preview__remove",type:"button",title:"Remove this option",attrs:{"aria-label":`Remove ${s.label||"this option"}`},on:{pointerdown:i=>i.stopPropagation(),click:i=>{i.stopPropagation(),e.restructure(o=>{o.choices.splice(n,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(n=>{const i=n.choices.length+1;n.choices.push({label:`Option ${i}`,value:`Option ${i}`})})}},children:[r("span",{text:"+ Add option"})]})),r("fieldset",{class:"atf-choices",children:[t]})}function gs(a,e,t){return a.type==="heading"?V({value:a.label,placeholder:"Section heading…",class:"atf-heading",bind:"label",onInput:s=>t.edit(n=>{n.label=s}),onCommit:()=>t.restructure(()=>{})}):a.type==="divider"?r("hr",{class:"atf-divider"}):a.type==="page_break"?r("div",{class:"atfb-preview__stack",children:[r("p",{class:"atfb-progress-name",children:[V({value:a.label,placeholder:"Name this step…",class:"atf-progress__label",bind:"label",onInput:s=>t.edit(n=>{n.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:[Te(String(a.prevLabel??""),"Back","atf-button--secondary","prevLabel",t,(s,n)=>{s.prevLabel=n}),Te(String(a.nextLabel??""),"Next","","nextLabel",t,(s,n)=>{s.nextLabel=n})]})]}):r("p",{class:"atfb-preview__summary",text:`${e?.label??a.type} — nothing is shown to the visitor here.`})}const Fe=new Map;function Ie(a){let e=Fe.get(a);return e||(e=I.mergeTags(a).catch(()=>[]),Fe.set(a,e)),e}function vs(a){Fe.delete(a)}function ys(a){const e=new Map;for(const t of a)for(const s of t.items)e.set(s.tag,s);return e}function ws(a,e){const t=ys(e);return a.replace(/\{[a-z_]+(?::[^}]*)?\}/gi,s=>{const n=t.get(s.toLowerCase());return n?n.sample:s})}function xs(a){return/\{[a-z_]+(?::[^}]*)?\}/i.test(a)}let te=null;function ie(){te?.remove(),te=null}typeof document<"u"&&(document.addEventListener("pointerdown",a=>{const e=a.target;e?.closest(".atfb-tagpick__open")||te&&!te.contains(e)&&ie()}),document.addEventListener("keydown",a=>{a.key==="Escape"&&te&&(ie(),a.stopPropagation())}));function it(a,e){const t=a.selectionStart??a.value.length,s=a.selectionEnd??a.value.length;a.value=a.value.slice(0,t)+e+a.value.slice(s);const n=t+e.length;a.setSelectionRange(n,n),a.focus(),a.dispatchEvent(new Event("input",{bubbles:!0}))}function _s(a){let e=a.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 ks(a,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"}),n=i=>{s.replaceChildren();const o=i.trim().toLowerCase();let l=0;for(const c of a){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),ie()}},children:[r("span",{class:"atfb-tagpick__item-main",children:[r("span",{class:"atfb-tagpick__label",text:h.label}),r("code",{class:"atfb-tagpick__tag",text:h.tag})]}),h.hint||h.sample?r("span",{class:"atfb-tagpick__meta",text:h.sample?`e.g. ${h.sample}`:h.hint}):null]}))}!l&&o&&s.append(r("p",{class:"atfb-tagpick__empty",text:`Nothing matches “${i}”.`}))};return n(""),t.addEventListener("input",()=>n(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 rt(a,e){const t=r("button",{class:"atfb-button atfb-button--ghost atfb-tagpick__open",type:"button",title:"Insert a value from the submission",children:[W("shortcode"),r("span",{text:"Insert a value"})]}),s=r("div",{class:"atfb-taggable",children:[a,r("div",{class:"atfb-taggable__tools",children:[t]})]}),n=e.preview===!1?null:r("p",{class:"atfb-taggable__preview"});n&&s.append(n);const i=()=>{if(n){if(!xs(a.value)){n.textContent="",n.hidden=!0;return}Ie(e.formId).then(o=>{n.hidden=!1,n.replaceChildren(r("span",{class:"atfb-taggable__preview-label",text:"Reads as"}),r("span",{text:ws(a.value,o)}))})}};return a.addEventListener("input",i),i(),t.addEventListener("click",o=>{if(o.stopPropagation(),te&&s.contains(te)){ie();return}ie(),Ie(e.formId).then(l=>{const c=ks(l,d=>{it(a,d),i()});s.append(c),te=c,(window.innerHeight-c.getBoundingClientRect().top_s(s))&&c.classList.add("atfb-tagpick--above"),c.querySelector(".atfb-tagpick__search")?.focus()})}),s}function ot(a,e){const t=parseFloat(String(a??""));return Number.isFinite(t)?t:e}function lt(a,e){let t=e[0];for(const s of e)Math.abs(s.at-a)a.accent??"#2271b1",apply:a=>({accent:a,"accent-soft":`color-mix( in srgb, ${a} 12%, transparent )`,"border-focus":a,"focus-ring-color":a,"button-bg":a,"button-bg-hover":`color-mix( in srgb, ${a} 88%, #000 )`})},{id:"roundness",label:"Roundness",hint:"Corners, everywhere at once.",kind:"scale",steps:qe.map(({value:a,label:e})=>({value:a,label:e})),owns:["radius-field","radius-button","radius-card","radius-check"],read:a=>lt(ot(a["radius-field"],4),qe),apply:a=>{const e=qe.find(t=>t.value===a)?.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:De.map(({value:a,label:e})=>({value:a,label:e})),owns:["pad-field-x","pad-field-y","gap-fields","gap-label","button-pad-x","button-pad-y"],read:a=>lt(ot(a["pad-field-y"],9),De),apply:a=>{const e=De.find(t=>t.value===a)?.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:Ss,owns:["shadow-field","shadow-field-focus","shadow-button","shadow-button-hover","shadow-card"],read:a=>{const e=a["shadow-card"]??"none";return e==="none"||e.trim()===""?a["shadow-field"]&&a["shadow-field"]!=="none"?"hard":"none":e.includes("0 1px")||e.includes("2px")?"subtle":"lifted"},apply:a=>{switch(a){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:Ls,owns:["field-style"],read:a=>a["field-style"]??"outline",apply:a=>({"field-style":a})},{id:"labels",label:"Labels",hint:"Where the question sits relative to the answer.",kind:"choice",steps:Es,owns:["label-position"],read:a=>a["label-position"]??"top",apply:a=>({"label-position":a})}]}function $s(a){return Oe().find(e=>e.owns.includes(a))??null}function ct(a){let e=a.activeSlug,t={...a.overrides},s=a.themes;const n=u=>{t=u,a.onOverridesReplaced?.({...u})},i=r("div",{class:"atfs-preview__frame"}),o=r("div",{class:"atfs-controls__body"}),l=r("div",{class:"atfs-quick"}),c=r("div",{class:"atfs-themes"}),d=Ee(async()=>{try{const u=await a.previewFor(e,t),f=i.closest(".atf-studio__preview"),b=f?.scrollTop??0;i.innerHTML=u,f&&(f.scrollTop=b),document.dispatchEvent(new CustomEvent("atf-refresh"))}catch(u){P(i),i.append(r("p",{class:"atfb-error",text:u instanceof Error?u.message:"Preview failed."}))}},180),h={"label-position":"atf-labels-","field-style":"atf-fields-"},g=(u,f,b)=>{const w=b.replace(/[^a-z0-9_-]/gi,"");for(const k of[...u.classList])k.startsWith(f)&&u.classList.remove(k);w&&u.classList.add(f+w)},v=u=>{const f=i.querySelector(".atf-form-wrap"),b=f?.querySelector(".atf-form");if(f)for(const[w,k]of Object.entries(u))k===""?f.style.removeProperty(`--atf-${w}`):f.style.setProperty(`--atf-${w}`,k),b&&h[w]&&g(b,h[w],k||(s.find(q=>q.slug===e)?.resolved[w]??""))},p=u=>{v(u.resolved);const f=i.querySelector(".atf-form");f&&(g(f,"atf-theme-",u.slug),f.classList.toggle("atf-is-dark",!!u.dark))},_=u=>t[u.token]!==void 0?t[u.token]:s.find(b=>b.slug===e)?.resolved?.[u.token]??u.default,y=()=>{P(c);for(const u of s){const f=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,n({}),a.onTheme(e),p(u),y(),Y(),H(),Dt()}}});c.append(f)}},x=()=>{const f={...s.find(b=>b.slug===e)?.resolved??{}};for(const[b,w]of Object.entries(t))f[b]=w;return f},F=u=>{const f=l.closest(".atf-studio__controls"),b=f?.scrollTop??0;u(),f&&(f.scrollTop=b)},H=Ee(()=>F(S),150),Y=()=>{const u=x(),f=l.querySelectorAll(".atfs-dial");Oe().forEach((b,w)=>{const k=f[w];if(!k)return;const q=b.read(u);if(b.kind==="colour"){const T=k.querySelector('input[type="color"]'),Z=k.querySelector("input.atfb-input");T&&document.activeElement!==T&&(T.value=me(q)),Z&&document.activeElement!==Z&&(Z.value=q);return}k.querySelectorAll(".atfs-segment").forEach((T,Z)=>{const Q=b.steps?.[Z]?.value===q;T.classList.toggle("is-on",Q),T.setAttribute("aria-pressed",String(Q))})})},A=(u,f)=>{const b=u.apply(f,x());for(const[w,k]of Object.entries(b))t[w]=k,a.onOverride(w,k);v(b),Y(),H()},C=()=>{P(l);const u=x();for(const f of Oe()){const b=f.read(u);let w;if(f.kind==="colour"){const k=r("input",{class:"atfs-color",type:"color",value:me(b),attrs:{"aria-label":f.label},on:{input:q=>A(f,q.target.value)}});w=r("div",{class:"atfs-color-row",children:[k,L(b,q=>A(f,q))]})}else w=r("div",{class:`atfs-segmented atfs-segmented--${f.kind}`,attrs:{role:"group","aria-label":f.label},children:(f.steps??[]).map(k=>r("button",{class:`atfs-segment${k.value===b?" is-on":""}`,type:"button",text:k.label,attrs:{"aria-pressed":k.value===b},on:{click:()=>A(f,k.value)}}))});l.append(r("div",{class:"atfs-dial",children:[r("div",{class:"atfs-dial__head",children:[r("span",{class:"atfs-dial__label",text:f.label}),r("span",{class:"atfs-dial__hint",text:f.hint})]}),w]}))}},S=()=>{P(o);const u=new Map;for(const b of a.tokens){const w=u.get(b.group)??[];w.push(b),u.set(b.group,w)}const f={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[b,w]of u)o.append(r("details",{class:"atfs-group",attrs:{open:b==="colour"},children:[r("summary",{text:f[b]??b}),...w.map(k=>O(k))]}))},O=u=>{const f=_(u),b=T=>{T===""?delete t[u.token]:t[u.token]=T,a.onOverride(u.token,T),v({[u.token]:T})};let w;if(u.control==="color"){const T=r("input",{class:"atfs-color",type:"color",value:me(f),attrs:{"aria-label":`${u.label} colour`},on:{input:Q=>{const oe=Q.target.value;Z.value=oe,b(oe)}}}),Z=L(f,Q=>{T.value=me(Q),b(Q)});w=r("div",{class:"atfs-color-row",children:[T,Z]})}else if(u.control==="select")w=M(f,(u.options??[]).map(T=>({value:T,label:T})),b);else if(u.control==="length"){const T=parseFloat(f)||0,Z=u.unit??"px",Q=u.token.includes("radius")?60:80,oe=r("input",{class:"atfs-range",type:"range",value:String(T),attrs:{min:"0",max:String(Q),step:"1","aria-label":u.label},on:{input:we=>{const Mt=`${we.target.value}${Z}`;Ot.value=Mt,b(Mt)}}}),Ot=L(f,we=>{oe.value=String(parseFloat(we)||0),b(we)});w=r("div",{class:"atfs-length-row",children:[oe,Ot]})}else w=L(f,b);const k=m(u.label,w),q=$s(u.token);return q&&(k.classList.add("is-dialled"),k.append(r("span",{class:"atfs-owned",text:q.label}))),t[u.token]!==void 0&&(k.classList.add("is-overridden"),k.append(r("button",{class:"atfs-reset",type:"button",text:"Reset",attrs:{"aria-label":`Reset ${u.label} to the theme's value`},on:{click:()=>{b(""),F(S)}}}))),k},$=async()=>{const u=s.find(w=>w.slug===e),f=u?`${u.label} (mine)`:"My theme",b=window.prompt("Name this theme",f);if(b)try{const w={};for(const q of a.tokens){const T=_(q);T!==q.default&&(w[q.token]=T)}const k=await I.saveTheme({label:b,tokens:w});s=[...s.filter(q=>q.slug!==k.slug),k],a.onThemesChanged(s),e=k.slug,n({}),a.onTheme(e),y(),Y(),F(S),R("Theme saved",k.label)}catch(w){R("Could not save the theme",w instanceof Error?w.message:"","error")}},B=async()=>{const u=s.find(f=>f.slug===e);if(u?.custom&&await pe(`Delete “${u.label}”? Forms using it fall back to Clean.`,"Delete theme"))try{await I.deleteTheme(u.id),s=s.filter(b=>b.slug!==u.slug),a.onThemesChanged(s),e="clean",n({}),a.onTheme(e),y(),Y(),F(S);const f=s.find(b=>b.slug===e);f&&p(f)}catch(f){R("Could not delete the theme",f instanceof Error?f.message:"","error")}},N=async()=>{const u=s.find(w=>w.slug===e),f={label:u?.label??e,tokens:{...u?.tokens??{},...t}},b=JSON.stringify(f,null," ");try{await navigator.clipboard.writeText(b),R("Theme copied","Paste it into another site to import it.")}catch{window.prompt("Copy this theme",b)}},re=()=>{const u=window.prompt("Paste a theme");if(u)try{const f=JSON.parse(u);if(!f.tokens||typeof f.tokens!="object")throw new Error("That does not look like a theme.");for(const[b,w]of Object.entries(f.tokens))t[b]=String(w),a.onOverride(b,String(w));v({...t}),Y(),F(S)}catch(f){R("Could not read that theme",f instanceof Error?f.message:"","error")}},qt=E("Delete",()=>void B(),"danger"),Dt=()=>{const u=s.find(f=>f.slug===e);qt.hidden=!u?.custom};return y(),C(),S(),d(),Dt(),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:[E("Save as a theme",()=>void $(),"primary"),E("Export",()=>void N()),E("Import",re),qt]})]}),c]}),r("div",{class:"atf-studio__panes",children:[r("aside",{class:"atf-studio__controls",children:[r("p",{class:"atfb-hint",text:"Changes apply to this form only, until you save them as a theme."}),l,r("details",{class:"atfs-advanced",children:[r("summary",{text:"Every setting"}),r("p",{class:"atfb-hint",text:"The full token list. The dials above write into it."}),o]})]}),r("main",{class:"atf-studio__preview",children:[r("h2",{class:"screen-reader-text",text:"Live preview"}),i]})]})]})}async function Me(){const a=document.querySelector("[data-atfs-root]");if(!a||a.dataset.atfsMounted)return;a.dataset.atfsMounted="1",Ze(a);const e=a.querySelector("[data-atfs-bar]"),t=a.querySelector(".atfs__body")??a;try{const[s,n,i]=await Promise.all([I.config(),I.listThemes(),I.listForms()]);if(!i.length){P(t),t.append(r("div",{class:"atfb-empty",children:[r("h2",{text:"No forms to preview"}),r("p",{text:"Make a form first — a theme needs something to dress."})]}));return}let o=i[0],l={},c=o.theme;const d=()=>{P(t),t.append(ct({themes:n,tokens:s.tokens,activeSlug:c,overrides:l,standalone:!0,onTheme:h=>{c=h},onOverride:(h,g)=>{g===""?delete l[h]:l[h]=g},onOverridesReplaced:h=>{l={...h}},previewFor:async(h,g)=>{const v=await I.getForm(o.id);v.schema.settings.theme=h,v.schema.settings.themeOverrides=g;const{html:p}=await I.preview(o.id,{schema:v.schema,theme:h});return p},onThemesChanged:h=>{n.length=0,n.push(...h)}}))};e&&(P(e),e.append(r("span",{class:"atfs__label",text:"Preview against"}),M(String(o.id),i.map(h=>({value:String(h.id),label:h.title||"(untitled)"})),h=>{o=i.find(g=>String(g.id)===h)??i[0],l={},d()}))),d()}catch(s){P(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 Me()):Me(),document.addEventListener("os-window-content-loaded",()=>void Me()));function me(a){const e=a.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 As={min:-1,max:-1,sum:-1,avg:-1,round:-1,ceil:1,floor:1,abs:1,sqrt:1,pow:2},dt={"+":{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 Cs(a,e,t=[]){if(!a||!a.trim()||a.length>2e3)return null;const s=Ts(a,e,t),n=Ds(s);if(!n)return null;const i=Ms(n);if(!i)return null;const o=Ns(i);return o===null||!Number.isFinite(o)?null:o}function Ts(a,e,t){return a.replace(/\{([a-zA-Z0-9_]+)(?:\.([a-zA-Z0-9_]+))?\}/g,(s,n,i,o)=>Fs(a,o,s.length,n,i??"",e,t))}function Fs(a,e,t,s,n,i,o){const l=Is(o,s),c=Object.prototype.hasOwnProperty.call(i,s)?i[s]:null;if(n===""){const p=l?.type==="repeater"?ht(c).length:Ne(c,l);return ut(p)}const h=(l?.fields??[]).find(p=>p.id===n)??null,g=ht(c).map(p=>Ne(p[n]??"",h));if(!g.length)return"0";const v=g.map(ut);return qs(a,e,t)?v.join(", "):v.length===1?v[0]:`( ${v.join(" + ")} )`}function Is(a,e){for(const t of a){if(t.id===e)return t;for(const s of t.fields??[])if(s.id===e)return s}return null}function qs(a,e,t){const s=a.slice(e+t).replace(/^\s+/,"");if(s===""||s[0]!==")")return!1;const n=a.slice(0,e).replace(/\s+$/,""),i=/([a-zA-Z_][a-zA-Z0-9_]*)\s*\($/.exec(n);return!!i&&["sum","avg","min","max"].includes(i[1].toLowerCase())}function ht(a){if(!Array.isArray(a))return[];const e=[];for(const t of a){if(!t||typeof t!="object"||Array.isArray(t))continue;Object.values(t).some(n=>n!==""&&n!==null&&n!==void 0&&n!==!1&&!(Array.isArray(n)&&!n.length))&&e.push(t)}return e}function ut(a){const e=a.toFixed(10).replace(/0+$/,"").replace(/\.$/,"");return e===""||e==="-"?"0":e}function Ne(a,e){if(typeof a=="boolean")return a?1:0;if(Array.isArray(a))return a.reduce((n,i)=>n+Ne(i,e),0);if(a==null||a==="")return 0;const t=e?.choices??[];for(const n of t)if(String(n.value)===String(a)){if(typeof n.price=="number")return n.price;if(typeof n.points=="number")return n.points;break}const s=Number(a);return Number.isFinite(s)?s:0}function Ds(a){const e=[];let t=0;for(;ti.precedence||l.precedence===i.precedence&&!i.right){e.push(t.pop());continue}break}t.push(n);break}case"(":t.push(n);break;case")":{for(;t.length&&t[t.length-1].type!=="(";)e.push(t.pop());if(!t.length)return null;if(t.pop(),t.length&&t[t.length-1].type==="function"){const i=t.pop();i.arity=s.length?s.pop():1,e.push(i)}break}}for(;t.length;){const n=t.pop();if(n.type==="(")return null;e.push(n)}return e}function Ns(a){const e=[];for(const t of a)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(),n=e.pop();switch(t.value){case"+":e.push(n+s);break;case"-":e.push(n-s);break;case"*":e.push(n*s);break;case"/":e.push(s===0?0:n/s);break;case"%":e.push(s===0?0:n%s);break;case"^":e.push(Math.pow(n,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 Ps=["sum","min","max","avg","round","ceil","floor","abs","sqrt","pow"],pt=["number","range","scale","rating","total","select","multiselect","radio","checkboxes","switch","quiz"];function Re(a,e){return a.filter(t=>t.id!==e&&pt.includes(t.type))}function Bs(a){const e=[];for(const t of a){if(t.type!=="repeater")continue;const s=t.label||t.id;e.push({label:`${s} (how many)`,insert:`{${t.id}}`});for(const n of t.fields??[])pt.includes(n.type)&&e.push({label:`${s} · ${n.label||n.id}`,insert:`{${t.id}.${n.id}}`})}return e}function js(a,e){const t={};Re(a,e).forEach((s,n)=>{t[s.id]=n+1});for(const s of a){if(s.type!=="repeater"||s.id===e)continue;const n=s.fields??[],i=o=>Object.fromEntries(n.map((l,c)=>[l.id,c+1+o]));t[s.id]=[i(0),i(1)]}return t}function Ws(a){const e=r("div",{class:"atfb-overlay"}),t=()=>{e.remove(),document.removeEventListener("keydown",s)},s=p=>{p.key==="Escape"&&t()},n=r("textarea",{class:"atfb-input atfb-formula__input",attrs:{rows:"3","aria-label":"Formula"}});n.value=String(a.field.formula??"");const i=r("p",{class:"atfb-formula__result",attrs:{"aria-live":"polite"}}),o=js(a.fields,a.field.id),l=()=>{const p=n.value.trim();if(p===""){i.textContent="Empty. Reference a question below to start.",i.classList.remove("is-error");return}const _=Cs(p,o,a.fields);if(_===null){i.textContent="This does not compute yet — check the braces and parentheses.",i.classList.add("is-error");return}const y=Re(a.fields,a.field.id).map((x,F)=>`${x.label||x.id} = ${F+1}`).concat(a.fields.filter(x=>x.type==="repeater"&&x.id!==a.field.id).map(x=>`${x.label||x.id} = 2 sample rows`)).join(", ");i.textContent=`With sample answers (${y}): ${_}`,i.classList.remove("is-error")};n.addEventListener("input",l);const c=(p,_,y=0)=>r("button",{class:"atfb-formula__chip",type:"button",text:p,on:{click:()=>{if(it(n,_),y>0){const x=(n.selectionStart??n.value.length)-y;n.setSelectionRange(x,x)}}}}),d=Re(a.fields,a.field.id),h=Bs(a.fields),g=r("div",{class:"atfb-formula__chips",children:d.length||h.length?[...d.map(p=>c(p.label||p.id,`{${p.id}}`)),...h.map(p=>c(p.label,p.insert))]:[r("p",{class:"atfb-hint",text:"No number-shaped questions yet — add a number, scale or priced choice field and it appears here."})]}),v=r("div",{class:"atfb-formula__chips",children:Ps.map(p=>c(`${p}()`,`${p}()`,1))});e.append(r("div",{class:"atfb-modal atfb-formula",attrs:{role:"dialog","aria-label":"Formula editor"},children:[r("h2",{text:"Formula"}),n,i,m("Your questions",g,"Click one to reference its answer."),m("Functions",v),r("div",{class:"atfb-modal__actions",children:[E("Cancel",t),E("Save formula",()=>{a.onSave(n.value.trim()),t()},"primary")]})]})),e.addEventListener("click",p=>{p.target===e&&t()}),document.addEventListener("keydown",s),a.root.append(e),l(),n.focus(),n.setSelectionRange(n.value.length,n.value.length)}const Hs="allterrain-forms/form";function Us(){return window.wp?.os?.relations??null}function ft(a){const e=a.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 zs=6e3,mt=120;function bt(a,e){const t=Us();if(Pe.set(a,e),!t?.set)return;const s=i=>{if(be.get(a)!==n)return;const o=ft(a);if(!o){Date.now()s(i),mt);return}try{t.set(o,e)}catch(c){gt||(gt=!0,console.error("[AllTerrain Forms] The shell refused a window identity.",c,e)),be.delete(a);return}if(!e||t.get?.(o)?.id===e.id||Date.now()>=i){be.delete(a);return}window.setTimeout(()=>s(i),mt)},n=Symbol("atf-identity");be.set(a,n),s(Date.now()+zs)}let gt=!1;const be=new WeakMap,Pe=new Map;function Ys(){for(const[a,e]of Pe){if(!a.isConnected){Pe.delete(a);continue}bt(a,e)}}if(typeof document<"u")for(const a of["os-window-content-loaded","os-window-opened"])document.addEventListener(a,()=>Ys());function Gs(a,e){return{type:Hs,id:a.id,label:a.title||"Untitled form",related:[{id:`allterrain-forms/entries-${a.id}`,label:"Entries for this form",url:`${e}admin.php?page=allterrain-forms-entries&form=${a.id}`,group:"allterrain-forms",groupLabel:"Forms",icon:"dashicons-list-view"}]}}const ge={letters:{label:"Letters",chars:"A-Za-zÀ-ÖØ-öø-ÿ"},numbers:{label:"Numbers",chars:"0-9"},spaces:{label:"Spaces",chars:" "},punctuation:{label:`Punctuation ( . , ! ? ' " - )`,chars:`.,!?'"()\\-:;`},symbols:{label:"Symbols ( @ # & _ / + )",chars:"@#&_/+*%="}};function Zs(){return{mode:"blocks",starts:"",ends:"",contains:"",notContains:"",chars:[],minLen:"",maxLen:"",regex:"",message:"",tests:[]}}function vt(a){const e=Zs();let t;try{t=JSON.parse(a)}catch{return e}if(!t||typeof t!="object")return e;const s=t;e.mode=s.mode==="regex"?"regex":"blocks";for(const n of["starts","ends","contains","notContains","minLen","maxLen","regex","message"])typeof s[n]=="string"&&(e[n]=s[n]);return Array.isArray(s.chars)&&(e.chars=s.chars.filter(n=>typeof n=="string"&&n in ge)),Array.isArray(s.tests)&&(e.tests=s.tests.filter(n=>typeof n=="string").slice(0,10)),e}function ve(a){return a.replace(/[.*+?^${}()|[\]\\/]/g,"\\$&")}function ye(a){if(a.mode==="regex")return a.regex.trim();const e=[],t=a.minLen.trim(),s=a.maxLen.trim();(t!==""||s!=="")&&e.push(`(?=.{${t===""?"0":parseInt(t,10)||0},${s===""?"":parseInt(s,10)||""}}$)`),a.contains!==""&&e.push(`(?=.*${ve(a.contains)})`),a.notContains!==""&&e.push(`(?!.*${ve(a.notContains)})`),a.starts!==""&&e.push(`(?=${ve(a.starts)})`),a.ends!==""&&e.push(`(?=.*${ve(a.ends)}$)`);const n=a.chars.map(o=>ge[o]?.chars??"").join(""),i=n?`[${n}]*$`:".*$";return!e.length&&!n?"":`^${e.join("")}${i}`}function yt(a){if(a.mode==="regex")return a.regex.trim()?`Matches the expression ${a.regex.trim()}`:"";const e=[];if(a.starts&&e.push(`starts with “${a.starts}”`),a.ends&&e.push(`ends with “${a.ends}”`),a.contains&&e.push(`contains “${a.contains}”`),a.notContains&&e.push(`never contains “${a.notContains}”`),a.chars.length){const i=a.chars.map(o=>(ge[o]?.label??o).split(" (")[0].toLowerCase());e.push(`uses only ${i.join(" and ")}`)}const t=a.minLen.trim(),s=a.maxLen.trim();return t&&s?e.push(`is ${t}–${s} characters long`):t?e.push(`is at least ${t} characters long`):s&&e.push(`is at most ${s} characters long`),e.length?`The answer ${e.length>1?`${e.slice(0,-1).join(", ")}, and ${e[e.length-1]}`:e[0]}.`:""}function Ks(a,e){const t=ye(a);if(t==="")return null;try{return new RegExp(t).test(e)}catch{return null}}function Xs(){return window.wp?.os?.windowManager??null}const wt="allterrain-forms-validation";function Vs(a){const e=Xs(),t=ft(a.root);if(e?.openChild&&t){Js(e,t,a);return}Qs(a)}function Js(a,e,t){const s=`${wt}-${t.field.id}`;a.getById?.(s)&&a.remove?.(s);const n={saved:!1},i=()=>{const o=a.getById?.(s);o?.close?o.close():a.remove?.(s)};a.openChild?.(e,{id:s,baseId:wt,url:`#${s}`,title:`Custom rule — ${t.field.label||"this question"}`,icon:"dashicons-yes-alt",native:!0,width:560,height:700,minWidth:440,minHeight:480,ephemeral:!0,autofocus:".atfb-valwin__pane:not([hidden]) input, .atfb-valwin__pane:not([hidden]) textarea",render:o=>{const l=r("div",{class:"atfa atfb-valwin atfb-valwin--window"}),c=r("div",{class:"atfb-valwin__scroll"});l.append(c),xt(c,t,n,i,"window");const d=c.querySelector(".atfb-modal__actions");d&&l.append(d),o.append(l)},onClose:()=>{n.saved||t.onCancel?.()}})}function Qs(a){const e=r("div",{class:"atfb-overlay"}),t={saved:!1},s=()=>{e.remove(),document.removeEventListener("keydown",n),t.saved||a.onCancel?.()},n=o=>{o.key==="Escape"&&s()},i=r("div",{class:"atfb-modal atfb-valwin",attrs:{role:"dialog","aria-label":"Custom validation rule"},children:[r("h2",{text:"Custom rule"})]});xt(i,a,t,s,"overlay"),e.append(i),e.addEventListener("click",o=>{o.target===e&&s()}),document.addEventListener("keydown",n),a.root.append(e),e.querySelector(".atfb-valwin__pane:not([hidden]) input, .atfb-valwin__pane:not([hidden]) textarea")?.focus()}function xt(a,e,t,s,n){const i=vt(String(e.field.validationRecipe??""));i.mode==="blocks"&&ye(i)===""&&e.field.pattern&&(i.mode="regex",i.regex=String(e.field.pattern)),i.message||(i.message=String(e.field.messages?.invalid??""));const o=(S,O)=>{const $=r("input",{class:"atfb-input",value:i[S],placeholder:O,attrs:{type:"text"}});return $.addEventListener("input",()=>{i[S]=$.value,C()}),$},l=(S,O)=>{const $=r("input",{class:"atfb-input atfb-valwin__len",value:i[S],attrs:{type:"number",min:"0","aria-label":O}});return $.addEventListener("input",()=>{i[S]=$.value,C()}),$},c=r("div",{class:"atfb-valwin__chars",children:Object.entries(ge).map(([S,O])=>D(O.label,i.chars.includes(S),$=>{i.chars=$?[...i.chars,S]:i.chars.filter(B=>B!==S),C()}))}),d=r("div",{class:"atfb-valwin__pane",children:[m("Starts with",o("starts","e.g. AT-")),m("Ends with",o("ends","e.g. -2026")),m("Must contain",o("contains","e.g. @")),m("Must not contain",o("notContains","e.g. spaces? type one")),m("Only these characters",c,"Leave every box unticked to allow anything."),m("Length",r("div",{class:"atfb-valwin__lengths",children:[r("span",{text:"between"}),l("minLen","Minimum length"),r("span",{text:"and"}),l("maxLen","Maximum length"),r("span",{text:"characters"})]}),"Leave a box empty for no limit.")]}),h=r("textarea",{class:"atfb-input atfb-valwin__regex",attrs:{rows:"2","aria-label":"Regular expression",placeholder:"^AT-[0-9]{4}$"}});h.value=i.regex,h.addEventListener("input",()=>{i.regex=h.value,C()});const g=r("div",{class:"atfb-valwin__pane",children:[m("Expression",h,"A regular expression, without slashes. Checked against the whole answer only if you anchor it with ^ and $.")]}),v=[["blocks","Easy blocks"],["regex","Expression (advanced)"]],p=S=>{i.mode=S,d.hidden=S!=="blocks",g.hidden=S!=="regex",C()},y=(()=>{if(z("os-segmented")&&z("os-segment")){const $=document.createElement("os-segmented");$.setAttribute("value",i.mode),$.setAttribute("label","How to write the rule"),$.classList.add("atfb-valwin__tabs");for(const[B,N]of v){const re=document.createElement("os-segment");re.setAttribute("value",B),re.textContent=N,$.append(re)}return $.addEventListener("os-pick",B=>{const N=B.detail?.value;(N==="blocks"||N==="regex")&&($.setAttribute("value",N),p(N))}),$}const S=r("div",{class:"atfb-valwin__tabs",attrs:{role:"tablist"}}),O=()=>{S.replaceChildren(...v.map(([$,B])=>{const N=i.mode===$;return r("button",{class:`atfb-valwin__tab${N?" is-active":""}`,type:"button",text:B,attrs:{role:"tab","aria-selected":N?"true":"false"},on:{click:()=>{p($),O()}}})}))};return O(),S})();d.hidden=i.mode!=="blocks",g.hidden=i.mode!=="regex";const x=r("input",{class:"atfb-input",value:i.message,placeholder:"That is not in the expected format.",attrs:{type:"text"}});x.addEventListener("input",()=>{i.message=x.value});const F=r("p",{class:"atfb-valwin__summary",attrs:{"aria-live":"polite"}}),H=r("div",{class:"atfb-valwin__samples"}),Y=S=>{const O=r("span",{class:"atfb-valwin__verdict",attrs:{"aria-live":"polite"}}),$=r("input",{class:"atfb-input",value:S,placeholder:"Type a sample answer…",attrs:{type:"text"}});$.addEventListener("input",()=>C()),H.append(r("div",{class:"atfb-valwin__sample",children:[$,O]}))};for(const S of i.tests.length?i.tests:["","",""])Y(S);const A=()=>Array.from(H.querySelectorAll("input")).map(S=>S.value),C=()=>{const S=yt(i);F.textContent=S||"Nothing yet — fill in a block above and the rule appears here in plain words.";for(const O of Array.from(H.querySelectorAll(".atfb-valwin__sample"))){const $=O.querySelector("input"),B=O.querySelector(".atfb-valwin__verdict");if(!$||!B)continue;const N=$.value===""?null:Ks(i,$.value);B.textContent=N===null?"·":N?"✓ passes":"✗ fails",B.classList.toggle("is-pass",N===!0),B.classList.toggle("is-fail",N===!1)}};a.append(r("p",{class:"atfb-hint",text:`Describe what a good answer to “${e.field.label||"this question"}” looks like — no code needed.`}),y,d,g,r("div",{class:"atfb-valwin__try",children:[r("h3",{text:"Try it out"}),F,H,E("Add another sample",()=>{Y(""),C()},"ghost","plus-alt2")]}),m("When it fails, say",x,"Shown to the visitor when their answer breaks the rule. Leave empty for the default wording."),r("div",{class:"atfb-modal__actions",children:[E(n==="window"?"Close without saving":"Cancel",s),E("Save rule",()=>{const S=ye(i);i.tests=A().filter(O=>O!=="").slice(0,10),t.saved=!0,e.onSave({pattern:S,recipe:i,message:i.message.trim()}),s()},"primary")]})),C()}const en=["Contact","Numbers & codes","Text shape","Web"],_t=[{slug:"email",label:"An email address",group:"Contact",example:"jane@example.com",pattern:"^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$",message:"That does not look like an email address."},{slug:"phone",label:"A phone number",group:"Contact",example:"+34 612 345 678",pattern:"^(?=(?:[^0-9]*[0-9]){5,})\\+?[0-9 ().-]{5,24}$",message:"That does not look like a phone number."},{slug:"handle",label:"A username or @handle",group:"Contact",example:"@yourname",pattern:"^@?[A-Za-z0-9_]{2,30}$",message:"That does not look like a username."},{slug:"digits",label:"Numbers only",group:"Numbers & codes",example:"12345",pattern:"^[0-9]+$",message:"Numbers only, please."},{slug:"decimal",label:"A number, decimals allowed",group:"Numbers & codes",example:"3.14",pattern:"^-?[0-9]+([.,][0-9]+)?$",message:"That does not look like a number."},{slug:"price",label:"A price",group:"Numbers & codes",example:"19.99",pattern:"^[0-9]+([.,][0-9]{1,2})?$",message:"That does not look like a price."},{slug:"zip_us",label:"A ZIP code (US)",group:"Numbers & codes",example:"90210",pattern:"^[0-9]{5}(-[0-9]{4})?$",message:"That does not look like a ZIP code."},{slug:"postcode_uk",label:"A postcode (UK)",group:"Numbers & codes",example:"SW1A 1AA",pattern:"^[A-Za-z]{1,2}[0-9][A-Za-z0-9]? ?[0-9][A-Za-z]{2}$",message:"That does not look like a postcode."},{slug:"iban",label:"An IBAN",group:"Numbers & codes",example:"DE89 3704 0044 0532 0130 00",pattern:"^[A-Za-z]{2}[0-9]{2}(?: ?[A-Za-z0-9]){10,32}$",message:"That does not look like an IBAN."},{slug:"credit_card",label:"A card number",group:"Numbers & codes",example:"4242 4242 4242 4242",pattern:"^[0-9](?:[0-9 -]{9,21})?[0-9]$",message:"That does not look like a card number.",luhn:!0},{slug:"letters",label:"Letters only",group:"Text shape",example:"María López",pattern:"^[\\p{L}\\p{M} .'’-]+$",message:"Letters only, please."},{slug:"alphanumeric",label:"Letters and numbers only",group:"Text shape",example:"abc123",pattern:"^[A-Za-z0-9]+$",message:"Letters and numbers only, please."},{slug:"no_spaces",label:"One word, no spaces",group:"Text shape",example:"one-word",pattern:"^\\S+$",message:"No spaces allowed."},{slug:"url",label:"A web address",group:"Web",example:"https://example.com",pattern:"^(https?://)?([A-Za-z0-9-]+\\.)+[A-Za-z]{2,}([/?#]\\S*)?$",message:"That does not look like a web address."},{slug:"ip",label:"An IP address",group:"Web",example:"192.168.0.1",pattern:"^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$",message:"That does not look like an IP address."},{slug:"slug",label:"A URL slug",group:"Web",example:"my-page-title",pattern:"^[a-z0-9]+(-[a-z0-9]+)*$",message:"Lowercase letters, numbers and dashes only."},{slug:"hex_color",label:"A hex colour",group:"Web",example:"#3366ff",pattern:"^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$",message:"That does not look like a colour code."}];function tn(a){return _t.find(e=>e.slug===a)??null}function Be(){return window.wp?.os??null}const kt="allterrain-forms/preview",je="allterrain-forms-preview";function sn(a){const e=Be();if(!e?.registerTitleBarButton)return()=>{};const t=()=>{try{e.registerTitleBarButton({id:kt,label:"Preview this form",icon:"dashicons-visibility",placement:"right",order:90,match:s=>{const n=s?.id??s?.config?.id??"";return n==="allterrain-forms"||n.startsWith("allterrain-forms#")},onClick:()=>void St(a),owner:"allterrain-forms-builder"})}catch{}};return e.ready?e.ready(t):t(),()=>{try{e.unregisterTitleBarButton?.(kt)}catch{}}}async function St(a){a.isDirty()&&await a.save();const e=a.current();e&&Lt(e.id,e.title,e.previewUrl)}function Lt(a,e,t){const s=Be();if(!s?.windowManager?.open){window.open(t,"_blank","noopener");return}s.windowManager.open({id:`${je}-${a}`,baseId:je,url:t,title:`Preview: ${e}`,icon:"dashicons-visibility"})}function nn(a,e,t){if(!Be()?.windowManager?.open||!document.querySelector(`[data-window-id="${je}-${a}"]`))return;const i=t.includes("?")?"&":"?";Lt(a,e,`${t}${i}atf_r=${Date.now()}`)}const se="allterrain-forms/field",an=["openstation/file","desktop-mode/file","openstation/attachment"],Et="allterrain-forms/logic-map-v2";function J(a,e){return a.dataset.atfbBind=e,a}const $t={level:{key:"level",label:"Heading level",control:"select",hint:"Headings should step down one at a time.",options:[{value:"2",label:"Heading 2"},{value:"3",label:"Heading 3"},{value:"4",label:"Heading 4"},{value:"5",label:"Heading 5"},{value:"6",label:"Heading 6"}]},content:{key:"content",label:"HTML",control:"textarea",hint:"Shown as written. Scripts are stripped when the form is saved."},consenttext:{key:"consentText",label:"What they are agreeing to",control:"textarea",hint:"Shown beside the tick box. Links are allowed."},height:{key:"height",label:"Height",control:"number",hint:"In pixels."},columns:{key:"columns",label:"Columns",control:"number",hint:"How many pictures sit side by side."},multiple:{key:"multiple",label:"Let them choose more than one",control:"checkbox"},inline:{key:"inline",label:"Lay the options out in a row",control:"checkbox"},other:{key:"other",label:"Offer an “Other” box",control:"checkbox",hint:"Adds a final option with a box to type in."},filetypes:{key:"filetypes",label:"Accepted file types",control:"commas",hint:"Extensions, separated by commas. Empty accepts anything the site allows."},maxsize:{key:"maxsize",label:"Largest file",control:"number",hint:"In megabytes."},maxfiles:{key:"maxfiles",label:"How many files",control:"number"},minrows:{key:"minRows",label:"Fewest rows",control:"number",also:{key:"maxRows",label:"Most rows"}},itemlabel:{key:"itemLabel",label:"What is one row called?",control:"text",hint:"Names each card — “Attendee 1”, “Attendee 2” — and the Remove button. Also editable on the card itself."},endlabels:{key:"minLabel",label:"Label at the low end",control:"text",also:{key:"maxLabel",label:"Label at the high end"}},points:{key:"points",label:"Points if correct",control:"number"},minchoices:{key:"minChoices",label:"Fewest they may pick",control:"number",also:{key:"maxChoices",label:"Most they may pick"}}},rn={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 At(a,e,t){const s=a[e.key],n=i=>t(e.key,i);if(e.control==="checkbox")return D(e.label,!!s,n);if(e.control==="commas"){const i=Array.isArray(s)?s:[];return m(e.label,L(i.join(", "),o=>{n(o.split(",").map(l=>l.trim().replace(/^\./,"").toLowerCase()).filter(Boolean))}),e.hint)}return e.control==="select"?m(e.label,M(String(s??""),e.options??[],n),e.hint):e.control==="textarea"?m(e.label,he(String(s??""),n),e.hint):e.control==="number"?m(e.label,G(String(s??""),n),e.hint):m(e.label,J(L(String(s??""),n),e.key),e.hint)}function Ct(a,e){const t=new Set(a.map(n=>n.key).filter(Boolean));let s=a.length+1;return e.split(` -`).map(n=>n.trim()).filter(Boolean).map((n,i)=>{const o=a[i]?.key;if(o)return{key:o,label:n};for(;t.has(`r${s}`);)s+=1;return t.add(`r${s}`),{key:`r${s}`,label:n}})}function Tt(a,e,t){const s=a.findIndex(i=>i.id===e);if(s<0)return null;const n=Math.max(0,Math.min(t,a.length-1));return n===s?null:{from:s,to:n}}const We=[{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}"}],on=["About the person filling it in","The date and time","About this site"],He=(a,e)=>j?.i18n?.[a]??e;class Ft{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=Yt(Et)!=="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=Ee(()=>{this.save(!0)},2500),this.renderInspector=zt(()=>{if(P(this.inspector),!this.schema||(this.root.classList.toggle("atfb--build-only-panes",this.tab!=="build"),this.tab!=="build"))return;const t=this.selected?this.locateField(this.selected):void 0,s=t?.field,n=t?.parent??null;if(this.root.classList.toggle("atfb--has-selection",!!s),!s){this.inspector.append(r("div",{class:"atfb-placeholder",children:[r("p",{text:"Select a field to change it."}),r("p",{class:"atfb-hint",text:"Drag a field from the palette, or press one to add it to the end."})]}));return}const i=this.config?.fieldTypes.find(c=>c.type===s.type),o=i?.supports??[],l=(c,d)=>{const h=this.liveField(s.id);h&&(h[c]=d,this.markDirty(),this.renderCanvas())};this.inspector.append(r("h3",{class:"atfb-inspector__title",text:i?.label??s.type})),n?this.inspector.append(r("p",{class:"atfb-hint atfb-inspector__crumb",text:`Inside ${n.label||"a repeater"} — the visitor answers this once per ${String(n.itemLabel??"")||"row"}.`}),r("p",{class:"atfb-hint",text:`Formulas aggregate it as {${n.id}.${s.id}} — e.g. sum( {${n.id}.${s.id}} ).`})):this.inspector.append(r("p",{class:"atfb-hint",text:`Reference this field as {field:${s.id}}`})),o.includes("label")&&this.inspector.append(m("Label",J(L(s.label,c=>l("label",c)),"label"))),o.includes("placeholder")&&this.inspector.append(m("Placeholder",J(L(s.placeholder,c=>l("placeholder",c)),"placeholder"))),o.includes("hint")&&this.inspector.append(m("Hint",J(L(s.hint,c=>l("hint",c)),"hint"),"Shown under the field, and read out with it.")),o.includes("nextlabel")&&this.inspector.append(m("Next button",J(L(String(s.nextLabel??""),c=>l("nextLabel",c)),"nextLabel"),"Leave empty for “Next”.")),o.includes("prevlabel")&&this.inspector.append(m("Back button",J(L(String(s.prevLabel??""),c=>l("prevLabel",c)),"prevLabel"),"Leave empty for “Back”.")),o.includes("addlabel")&&this.inspector.append(m("Add button",J(L(String(s.addLabel??""),c=>l("addLabel",c)),"addLabel"),"Leave empty for “Add another”.")),o.includes("required")&&this.inspector.append(D("Required",s.required,c=>l("required",c))),o.includes("width")&&this.inspector.append(m("Width",M(s.width,[{value:"full",label:"Full width"},{value:"two-thirds",label:"Two thirds"},{value:"half",label:"Half"},{value:"third",label:"One third"},{value:"quarter",label:"One quarter"}],c=>l("width",c)))),i?.choices&&this.inspector.append(this.renderChoicesEditor(s,l)),this.renderTypeSettings(s,i,o,l),(s.type==="total"||o.includes("formula"))&&this.inspector.append(m("Formula",r("div",{class:"atfb-formula__row",children:[L(String(s.formula??""),c=>l("formula",c)),E("Formula editor",()=>Ws({root:this.root,fields:this.schema?.fields??[],field:s,onSave:c=>{l("formula",c),this.renderInspector()}}))]}),"Reference answers with braces — {f1} * {f2} + 10 — or open the editor and click them in."),m("Currency symbol",L(String(s.currency??""),c=>l("currency",c)))),this.inspector.append(this.renderValidationSection(s,o,l)),n||(this.inspector.append(this.renderLogicSection(s)),o.includes("prefill")&&this.inspector.append(this.prefillControl(s,l))),o.includes("css")&&this.inspector.append(m("CSS class",L(s.cssClass,c=>l("cssClass",c))))}),this.root=e,this.bar=e.querySelector("[data-atfb-bar]")??r("div"),this.palette=e.querySelector("[data-atfb-palette]")??r("div"),this.canvas=e.querySelector("[data-atfb-canvas]")??r("div"),this.inspector=e.querySelector("[data-atfb-inspector]")??r("div"),this.canvas.addEventListener("click",t=>{const s=t.target;if(!(this.tab!=="build"||!this.selected||s.closest("[data-atfb-card], button, os-button, a, input, textarea, select, os-select, label, [contenteditable]"))){this.selected=null;for(const n of this.canvas.querySelectorAll(".atfb-card.is-selected"))n.classList.remove("is-selected");this.renderInspector()}})}async start(){try{const[s,n,i]=await Promise.all([I.config(),I.listThemes(),I.listForms()]);this.config=s,this.themes=n,this.forms=i}catch(s){this.fail(s);return}this.teardowns.push(Bt([se])),this.teardowns.push(sn({current:()=>this.form?{id:this.form.id,title:this.form.title,previewUrl:this.form.previewUrl}:null,isDirty:()=>this.dirty,save:()=>this.save(!0)}));const e=s=>{this.dirty&&(s.preventDefault(),s.returnValue="")};window.addEventListener("beforeunload",e),this.teardowns.push(()=>window.removeEventListener("beforeunload",e));const t=s=>{!(s.metaKey||s.ctrlKey)||s.key.toLowerCase()!=="z"||s.target?.closest("input, textarea, [contenteditable]")||(s.preventDefault(),this.travel(s.shiftKey?1:-1))};if(this.root.addEventListener("keydown",t),this.teardowns.push(()=>this.root.removeEventListener("keydown",t)),this.renderBar(),this.renderPalette(),this.forms.length){const s=ss("builder");await this.open(s&&this.forms.some(n=>n.id===s)?s:this.forms[0].id)}else this.renderFormsList()}destroy(){this.canvasTarget?.(),this.canvasTarget=null,this.teardowns.forEach(e=>e()),this.teardowns=[]}fail(e){P(this.bar),this.bar.append(r("p",{class:"atfb-error",text:e instanceof Error?e.message:"Something went wrong loading your forms."}))}renderBar(){P(this.bar);const e=M(String(this.form?.id??""),[...this.forms.map(n=>({value:String(n.id),label:n.title||"(untitled)"}))],n=>void this.open(Number(n)));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:n=>{this.form&&(this.form.title=n.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(([n,i])=>r("button",{class:`atfb-tab${this.tab===n?" is-active":""}`,type:"button",text:i,attrs:{role:"tab","aria-selected":this.tab===n},on:{click:()=>{this.tab=n,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),E("New",()=>void this.showTemplates(),"secondary","plus-alt2"),E("Export",()=>void this.exportForm(),"secondary","download"),E("Import",()=>void this.importForm(),"secondary","upload"),E("Preview",()=>void this.preview(),"secondary","visibility"),E("Entries",()=>this.openEntries(),"secondary","list-view"),this.logicMapButton(),this.saveButton()]}))}liveField(e){return this.locateField(e)?.field}locateField(e){const t=this.schema?.fields??[];for(let s=0;sthis.rebindCanvas(),{once:!0});return}this.renderCanvas()}snapshot(){if(!this.schema)return;const e=JSON.stringify(this.schema);this.history[this.historyAt]!==e&&(this.history=this.history.slice(0,this.historyAt+1),this.history.push(e),this.history.length>60&&this.history.shift(),this.historyAt=this.history.length-1,this.syncHistoryButtons())}travel(e){const t=this.historyAt+e;!this.schema||t<0||t>=this.history.length||(this.historyAt=t,this.schema=JSON.parse(this.history[t]),this.schema.fields.some(s=>s.id===this.selected)||(this.selected=null),this.markDirty(),this.renderBar(),this.renderCanvas(),this.renderInspector())}historyButton(e,t,s){const n=E(t,()=>this.travel(s),"secondary",e);return n.setAttribute("data-atfb-history",String(s)),n.disabled=!this.canTravel(s),n}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 n=await I.createForm({title:s.title??t.name.replace(/\.json$/i,""),schema:s.schema});this.forms.unshift({id:n.id,title:n.title,status:n.status,modified:n.modified,fields:n.schema.fields.length,theme:n.schema.settings.theme,entries:0,unread:0,views:0,submissions:0,shortcode:n.shortcode}),this.form=n,this.schema=n.schema,this.selected=null,this.dirty=!1,this.history=[],this.historyAt=-1,this.snapshot(),this.renderBar(),this.renderCanvas(),this.renderInspector(),R("Form imported",n.title)}catch(s){R("Could not import that file",s instanceof Error?s.message:"","error")}}),e.click()}saveButton(){const e=E("Save",()=>void this.save(),"primary");return e.disabled=!this.dirty,e.setAttribute("data-atfb-save",""),e}markDirty(){this.dirty=!0,this.editGeneration+=1;const e=this.bar.querySelector("[data-atfb-save]");e&&(e.disabled=!1),this.autosave()}async save(e=!1){if(!this.form||!this.schema)return;if(this.saveInFlight){this.queuedSave={silent:e&&(this.queuedSave?.silent??!0)};return}this.saveInFlight=!0;const t=this.editGeneration;try{const s=await I.updateForm(this.form.id,{title:this.form.title,schema:this.schema});if(t===this.editGeneration){this.form=s,this.schema=s.schema,this.dirty=!1,this.rebindCanvas();const i=this.bar.querySelector("[data-atfb-save]");i&&(i.disabled=!0)}vs(s.id);const n=this.forms.find(i=>i.id===s.id);n&&(n.title=s.title),nn(s.id,s.title,s.previewUrl),e||R("Form saved",s.title)}catch(s){R(He("saveFailed","Could not save"),s instanceof Error?s.message:"","error")}finally{this.saveInFlight=!1;const s=this.queuedSave;this.queuedSave=null,s&&this.save(s.silent)}}async openFormById(e){await this.open(e)}async open(e){if(!(this.dirty&&!await pe("You have unsaved changes. Discard them?"))){try{this.form=await I.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&&bt(this.root,Gs(this.form,j?.adminUrl??""))}async showTemplates(){if(!this.config)return;const e=r("div",{class:"atfb-overlay"}),t=i=>{i.key==="Escape"&&s()},s=()=>{e.remove(),document.removeEventListener("keydown",t)},n=r("div",{class:"atfb-templates",children:this.config.templates.map(i=>r("button",{class:"atfb-template",type:"button",on:{click:async()=>{s();try{const o=await I.createForm({template:i.slug});this.forms.unshift({id:o.id,title:o.title,status:o.status,modified:o.modified,fields:o.schema.fields.length,theme:o.schema.settings.theme,entries:0,unread:0,views:0,submissions:0,shortcode:o.shortcode}),this.form=o,this.schema=o.schema,this.selected=null,this.dirty=!1,this.history=[],this.historyAt=-1,this.snapshot(),this.renderBar(),this.renderCanvas(),this.renderInspector(),this.announceIdentity()}catch(o){R("Could not create the form",o instanceof Error?o.message:"","error")}}},children:[W(i.icon),r("strong",{text:i.label}),r("span",{text:i.description})]}))});e.append(r("div",{class:"atfb-modal",attrs:{role:"dialog","aria-label":"Start a new form"},children:[r("h2",{text:"Start a new form"}),n,this.archivedFormsSection(s),r("div",{class:"atfb-modal__actions",children:[E("Cancel",s)]})]})),e.addEventListener("click",i=>{i.target===e&&s()}),document.addEventListener("keydown",t),this.root.append(e),n.querySelector("button")?.focus()}archivedFormsSection(e){const t=r("div",{class:"atfb-archived"});return I.listArchivedForms().then(s=>{s.length&&t.append(r("h3",{class:"atfb-archived__title",text:"Or bring one back from the archive"}),...s.map(n=>r("div",{class:"atfb-archived__row",children:[r("div",{class:"atfb-archived__meta",children:[r("strong",{text:n.title||"(untitled)"}),r("span",{class:"atfb-hint",text:`${n.entries} ${n.entries===1?"entry":"entries"} · ${n.submissions} submissions · ${n.views} views`})]}),E("Restore",async()=>{try{const i=await I.unarchiveForm(n.id);e(),this.forms.unshift(i),R("Form restored",`${i.title||"(untitled)"} is back, with its entries and stats.`),await this.open(i.id)}catch(i){R("Could not restore the form",i instanceof Error?i.message:"","error")}},"secondary","undo")]})))}).catch(()=>{}),t}renderFormsList(){P(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."}),E("New form",()=>void this.showTemplates(),"primary","plus-alt2")]}))}openEntries(){const e=window.wp?.os;if(e?.openWindow){e.openWindow("allterrain-forms-entries");return}window.location.assign(`${j?.adminUrl??""}admin.php?page=allterrain-forms-entries`)}renderPalette(){if(!this.config)return;P(this.palette);const e=new Map;for(const s of this.config.fieldTypes){const n=e.get(s.group)??[];n.push(s),e.set(s.group,n)}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 n=s.target.value.toLowerCase().trim();this.palette.querySelectorAll(".atfb-chip").forEach(i=>{const o=(i.textContent??"").toLowerCase();i.hidden=n!==""&&!o.includes(n)}),this.palette.querySelectorAll(".atfb-group").forEach(i=>{const o=Array.from(i.querySelectorAll(".atfb-chip")).some(l=>!l.hidden);i.hidden=!o})}}});this.palette.append(t);for(const[s,n]of Object.entries(this.config.groups)){const i=e.get(s);i?.length&&this.palette.append(r("div",{class:"atfb-group",children:[r("h3",{class:"atfb-group__title",text:n}),r("div",{class:"atfb-group__items",children:i.map(o=>this.paletteChip(o))})]}))}}paletteChip(e){const t=r("button",{class:"atfb-chip",type:"button",title:e.description,attrs:{"data-atf-type":e.type},children:[W(e.icon),r("span",{text:e.label})]});return t.addEventListener("pointerdown",s=>{const n=r("div",{class:"atfb-chip atfb-chip--ghost",children:[W(e.icon),r("span",{text:e.label})]});K().start({payload:ke(se,t,{fieldType:e.type,isNew:!0},s,n),origin:s,onClickOnly:()=>this.addField(e.type)})}),t.addEventListener("click",s=>{K().recentlyEndedDrag()&&s.preventDefault()}),t}renderCanvas(){if(P(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:He("emptyCanvas","Drag a field from the left to begin.")})),this.schema.fields.forEach((s,n)=>{e.append(this.renderFieldCard(s,n))});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:"),n=We.some(v=>v.value===e.prefill),i=s?"query":n&&e.prefill||(e.prefill?"custom":""),o=r("div",{class:"atfb-prefill__detail"}),l=r("p",{class:"atfb-row__hint atfb-prefill__preview"}),c=v=>{if(o.replaceChildren(),l.replaceChildren(),v==="query"){const p=e.prefill.startsWith("query:")?e.prefill.slice(6):"";o.append(L(p,_=>{const y=_.trim();t("prefill",y?`query:${y}`:""),d(`query:${y}`)},"utm_source"),r("p",{class:"atfb-row__hint",text:"The name of the parameter in the link people arrive on."}))}v==="custom"&&o.append(L(e.prefill,p=>{t("prefill",p),d(p)},"myplugin:something"),r("p",{class:"atfb-row__hint",text:"For a source another plugin has added through atf_resolve_prefill."})),d(e.prefill)},d=v=>{if(l.replaceChildren(),!v)return;if(v.startsWith("query:")){const _=v.slice(6);_&&(l.textContent=`A visit to …/your-page/?${_}=abc opens the form with “abc” in it.`);return}const p=We.find(_=>_.value===v)?.tag;p&&Ie(this.form?.id??0).then(_=>{for(const y of _)for(const x of y.items)if(x.tag===p){const F=v.startsWith("user:");if(!x.sample){l.textContent="Empty unless the visitor is signed in.";return}l.textContent=F?`Opens with “${x.sample}” for you — empty for a visitor who is not signed in.`:`Opens with “${x.sample}”.`;return}})};c(i);const h=r("select",{class:"atfb-input atfb-select",on:{change:v=>{const p=v.target.value;t("prefill",p==="query"||p==="custom"?"":p),c(p)}}});h.append(r("option",{value:"",text:"Nothing — leave it empty",attrs:{selected:i===""}}));for(const v of on){const p=document.createElement("optgroup");p.label=v;for(const _ of We.filter(y=>y.group===v))p.append(r("option",{value:_.value,text:_.label,attrs:{selected:_.value===i}}));h.append(p)}const g=document.createElement("optgroup");return g.label="From the link they arrived on",g.append(r("option",{value:"query",text:"A parameter in the web address",attrs:{selected:i==="query"}})),h.append(g,r("option",{value:"custom",text:"Something else (advanced)",attrs:{selected:i==="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,t){const s=t.some(i=>i.kind==="field"&&i.missing),n=r("span",{class:`atfb-cond${s?" is-broken":""}`,attrs:{"aria-label":et(t)},children:[W("randomize"),...t.map(i=>this.renderConditionToken(e,i))]});return n.addEventListener("pointerdown",i=>i.stopPropagation()),n.addEventListener("click",i=>i.stopPropagation()),n.addEventListener("keydown",i=>i.stopPropagation()),n}editCondition(e,t,s=!0,n=""){const i=this.liveField(e)?.logic;if(i){if(t(i),this.markDirty(),!s){this.logicMap?.setEdges(fe(this.schema?.fields??[]));return}this.renderCanvas(),this.renderInspector(),n&&window.requestAnimationFrame(()=>{this.canvas.querySelector(`[data-cond="${CSS.escape(n)}"]`)?.focus()})}}condSelect(e,t,s,n,i){if(z("os-select")&&z("os-option")){const o=document.createElement("os-select");o.setAttribute("value",e),o.setAttribute("aria-label",n),o.setAttribute("data-cond",s),o.className="atfb-cond__control",o.title=n;for(const l of t){const c=document.createElement("os-option");c.setAttribute("value",l.value),c.textContent=l.label,o.append(c)}return o.addEventListener("os-pick",l=>{i(String(l.detail?.value??""))}),o}return r("select",{class:"atfb-cond__control atfb-cond__control--native",title:n,attrs:{"aria-label":n,"data-cond":s},on:{change:o=>i(o.target.value)},children:t.map(o=>r("option",{value:o.value,text:o.label,attrs:{selected:o.value===e}}))})}renderConditionToken(e,t){if(t.kind==="field"&&!t.missing)return r("button",{class:"atfb-cond__chip atfb-cond__chip--field",type:"button",text:t.text,title:"Go to this question",on:{click:n=>{n.stopPropagation(),this.selectField(t.fieldId),this.canvas.querySelector(`[data-atfb-card="${CSS.escape(t.fieldId)}"]`)?.scrollIntoView({block:"nearest",behavior:"smooth"})}}});if(t.kind==="verb")return r("button",{class:"atfb-cond__verb",type:"button",text:t.text,title:"Switch between showing and hiding this field when the condition matches.",attrs:{"data-cond":`${e.id}:verb`},on:{click:()=>this.editCondition(e.id,s=>{s.action=s.action==="hide"?"show":"hide"},!0,`${e.id}:verb`)}});if(t.kind==="join")return r("button",{class:"atfb-cond__join",type:"button",text:t.text,title:"Switch between needing every rule (and) or any one of them (or).",attrs:{"data-cond":`${e.id}:join`},on:{click:()=>this.editCondition(e.id,s=>{s.match=s.match==="all"?"any":"all"},!0,`${e.id}:join`)}});if(t.kind==="operator"){const s=`${e.id}:op:${t.ruleIndex}`;return this.condSelect(t.operator,Object.entries($e).map(([n,i])=>({value:n,label:i})),s,"How the answer is compared",n=>this.editCondition(e.id,i=>{const o=i.rules[t.ruleIndex];o&&(o.operator=n)},!0,s))}return t.kind==="value"?this.renderConditionValue(e,t):r("span",{class:"atfb-cond__chip atfb-cond__chip--missing",text:t.text})}renderConditionValue(e,t){const s=`${e.id}:value:${t.ruleIndex}`,n=this.schema?.fields.find(c=>c.id===t.sourceId),i=(c,d)=>this.editCondition(e.id,h=>{const g=h.rules[t.ruleIndex];g&&(g.value=c)},d,s);if(n?.choices?.length){const c=n.choices.map(d=>({value:d.value,label:d.label||d.value}));return t.raw!==""&&!n.choices.some(d=>d.value===t.raw)&&c.unshift({value:t.raw,label:t.text}),this.condSelect(t.raw,c,s,"The answer that triggers this",d=>i(d,!0))}const o=["number","range","scale","rating","total"].includes(n?.type??""),l=r("input",{class:"atfb-cond__chip atfb-cond__chip--value atfb-cond__value",value:t.raw,title:"The answer that triggers this. Edit it here.",attrs:{type:"text","aria-label":"The answer that triggers this","data-cond":s,inputmode:o?"decimal":void 0,size:String(Math.max(2,Math.min(24,t.raw.length||2)))}});return l.addEventListener("input",()=>{l.size=Math.max(2,Math.min(24,l.value.length||2)),i(l.value,!1)}),l.addEventListener("change",()=>i(l.value,!0)),l.addEventListener("keydown",c=>{c.key==="Enter"&&(c.preventDefault(),i(l.value,!0))}),l}section(e,t,s,n=!1){const i=r("details",{class:"atfb-section",attrs:{open:this.openSections.get(e)??n},children:[r("summary",{text:t}),...s]});return i.addEventListener("toggle",()=>this.openSections.set(e,i.open)),i}logicMapButton(){const e=fe(this.schema?.fields??[]).length>0,t=E(this.logicMapOn?"Hide logic":"Show logic",()=>{this.logicMapOn=!this.logicMapOn,Gt(Et,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=fe(t);if(!s.length||!this.logicMapOn)return;e.classList.add("has-logicmap");const n=new os(e);n.setEdges(s),n.highlight(this.selected??""),this.logicMap=n,e.addEventListener("pointerover",i=>{const o=i.target.closest("[data-atfb-card]");n.highlight(o?.dataset.atfbCard??this.selected??"")}),e.addEventListener("pointerleave",()=>n.highlight(this.selected??""))}renderFieldCard(e,t){const s=this.config?.fieldTypes.find(d=>d.type===e.type),n=this.selected===e.id,i=this.schema?.fields??[],o=as(e,i),l=rs(i).get(e.id)??0,c=r("div",{class:`atfb-card${n?" is-selected":""}`,attrs:{"data-atfb-card":e.id,"data-index":t,tabindex:"0",role:"button","aria-pressed":n,"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:[W("menu")]}),r("div",{class:"atfb-card__body",children:[r("div",{class:"atfb-card__head",children:[W(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]}),at(e,s,{edit:d=>{const h=this.liveField(e.id);if(!h)return;d(h),this.markDirty(),this.syncInspector(h);const g=this.selected?this.locateField(this.selected):void 0;g?.parent&&g.parent.id===h.id&&this.syncInspector(g.field)},restructure:d=>{const h=this.liveField(e.id);h&&(this.snapshot(),d(h),this.markDirty(),this.renderCanvas(),this.renderInspector())},types:d=>this.config?.fieldTypes.find(h=>h.type===d),selectedId:this.selected}),o.length?this.renderCondition(e,o):null]}),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:ke(se,c,{fieldId:e.id,field:e,isNew:!1},d),origin:d})}),c}requiredToggle(e){return r("button",{class:`atfb-req${e.required?" is-on":""}`,type:"button",text:e.required?"Required":"Optional",title:e.required?"This must be answered. Click to make it optional.":"Click to make this required.",attrs:{"aria-pressed":e.required?"true":"false"},on:{pointerdown:s=>s.stopPropagation(),click:s=>{s.stopPropagation(),e.required=!e.required,this.markDirty(),this.renderCanvas(),this.renderInspector()}}})}cardAction(e,t,s){return r("button",{class:"atfb-card__action",type:"button",title:t,attrs:{"aria-label":t},on:{click:n=>{n.stopPropagation(),s()}},children:[W(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:o=>o.type===se,onEnter:()=>e.classList.add("is-dropping"),onLeave:()=>{e.classList.remove("is-dropping"),t.remove()},onDrop:(o,l)=>{e.classList.remove("is-dropping"),t.remove();const c=o.payload.data,d=c.fieldId?this.canvas.querySelector(`[data-atfb-card="${CSS.escape(c.fieldId)}"]`):null,h=Se(e,".atfb-card",l.clientY,d??void 0);if(c.isNew&&c.fieldType){this.addField(c.fieldType,h);return}if(c.fieldId&&this.locateField(c.fieldId)){this.relocateField(c.fieldId,null,h);return}c.field&&this.insertField({...c.field,id:""},h)}}),n=this.wireRepeaterZones(e),i=o=>{const l=o.detail;if(l?.payload?.type!==se||!e.classList.contains("is-dropping"))return;const c=l.payload.data?.fieldId?this.canvas.querySelector(`[data-atfb-card="${CSS.escape(l.payload.data.fieldId)}"]`):null,d=l.clientY??0,h=Se(e,".atfb-card",d,c??void 0),g=Array.from(e.querySelectorAll(".atfb-card")).filter(v=>v!==c);h>=g.length?e.append(t):g[h].before(t)};document.addEventListener("os.drag.move",i),this.canvasTarget=()=>{s(),n.forEach(o=>o()),document.removeEventListener("os.drag.move",i)}}wireRepeaterZones(e){const t=[];return e.querySelectorAll("[data-atfb-repeater-zone]").forEach(s=>{const n=s.dataset.atfbRepeaterZone??"";t.push(K().registerDropTarget({id:`atfb-repzone-${this.form?.id??0}-${n}`,element:s,accept:i=>{if(i.type!==se)return!1;const o=i.data;if(o.fieldId===n)return!1;const l=o.isNew?o.fieldType:this.locateField(o.fieldId??"")?.field.type??o.field?.type;return!!l&&this.allowedInRepeater(l)},onEnter:()=>s.classList.add("is-dropping"),onLeave:()=>s.classList.remove("is-dropping"),onDrop:(i,o)=>{s.classList.remove("is-dropping");const l=i.payload.data,c=l.fieldId?e.querySelector(`[data-atfb-subfield="${CSS.escape(l.fieldId)}"]`):null,d=Se(s,".atfb-subcard",o.clientY,c??void 0);if(l.isNew&&l.fieldType){this.addFieldToRepeater(l.fieldType,n,d);return}if(l.fieldId&&this.locateField(l.fieldId)){this.relocateField(l.fieldId,n,d);return}l.field&&this.insertFieldIntoRepeater({...l.field,id:""},n,d)}})),s.querySelectorAll(".atfb-subcard").forEach(i=>{const o=i.dataset.atfbSubfield??"";i.addEventListener("click",l=>{l.target.closest(".atfb-preview__remove")||(l.stopPropagation(),!K().recentlyEndedDrag()&&this.selectField(o))}),i.addEventListener("keydown",l=>{if(l.stopPropagation(),l.key==="Enter"||l.key===" "){l.preventDefault(),this.selectField(o);return}if(l.altKey&&(l.key==="ArrowUp"||l.key==="ArrowDown")){l.preventDefault();const c=this.locateField(o);c&&(this.relocateField(o,n,l.key==="ArrowUp"?c.index-1:c.index+1),window.requestAnimationFrame(()=>{this.canvas.querySelector(`[data-atfb-subfield="${CSS.escape(o)}"]`)?.focus()}));return}(l.key==="Delete"||l.key==="Backspace")&&(l.preventDefault(),this.deleteField(o))}),i.addEventListener("pointerdown",l=>{if(l.stopPropagation(),l.target.closest(".atfb-preview__remove"))return;const c=this.locateField(o);c&&K().start({payload:ke(se,i,{fieldId:o,parentId:n,field:c.field,isNew:!1},l),origin:l})})})}),t}addField(e,t){const s=this.buildField(e);s&&this.insertField(s,t)}buildField(e){const t=this.config?.fieldTypes.find(n=>n.type===e);return!t||!this.schema?void 0:{id:this.nextFieldId(),type:e,label:t.input?t.label:"",placeholder:"",hint:"",required:!1,width:"full",cssClass:"",default:"",choices:t.choices?[{label:"First choice",value:"first"},{label:"Second choice",value:"second"}]:[],logic:{enabled:!1,action:"show",match:"all",rules:[]},messages:{},prefill:"",...t.settings}}allowedInRepeater(e){const t=this.config?.fieldTypes.find(s=>s.type===e);return!t||!t.input?!1:!["repeater","page_break","file","signature","total"].includes(e)}addFieldToRepeater(e,t,s){if(!this.allowedInRepeater(e))return;const n=this.buildField(e);n&&this.insertFieldIntoRepeater(n,t,s)}insertFieldIntoRepeater(e,t,s){const n=this.liveField(t);if(!this.schema||!n||n.type!=="repeater")return;e.id||(e.id=this.nextFieldId()),this.snapshot(),Array.isArray(n.fields)||(n.fields=[]);const i=n.fields,o=s===void 0?i.length:Math.max(0,Math.min(s,i.length));i.splice(o,0,e),this.selected=e.id,this.markDirty(),this.renderCanvas(),this.renderInspector(),window.requestAnimationFrame(()=>{this.canvas.querySelector(`[data-atfb-subfield="${CSS.escape(e.id)}"]`)?.focus()})}relocateField(e,t,s){if(!this.schema)return;const n=this.locateField(e);if(!n)return;if(!n.parent&&!t){this.moveField(e,s);return}const i=t?this.liveField(t):null;if(t&&(!i||i.type!=="repeater"||e===t))return;i&&!Array.isArray(i.fields)&&(i.fields=[]);const o=i?i.fields:this.schema.fields;this.snapshot(),n.list.splice(n.index,1);const l=Math.max(0,Math.min(s,o.length));o.splice(l,0,n.field),this.selected=n.field.id,this.markDirty(),this.renderCanvas(),this.renderInspector()}insertField(e,t){if(!this.schema)return;e.id||(e.id=this.nextFieldId()),this.snapshot();const s=t===void 0?this.schema.fields.length:Math.max(0,Math.min(t,this.schema.fields.length));this.schema.fields.splice(s,0,e),this.selected=e.id,this.markDirty(),this.renderCanvas(),this.renderInspector(),window.requestAnimationFrame(()=>{this.canvas.querySelector(`[data-atfb-card="${CSS.escape(e.id)}"]`)?.focus()})}moveField(e,t){if(!this.schema)return;const s=Tt(this.schema.fields,e,t);if(!s)return;this.snapshot();const[n]=this.schema.fields.splice(s.from,1);this.schema.fields.splice(s.to,0,n),this.markDirty(),this.renderCanvas()}duplicateField(e){if(!this.schema)return;const t=this.locateField(e);if(!t)return;const s=JSON.parse(JSON.stringify(t.field)),n=this.usedFieldIds(),i=()=>{let o=n.size+1;for(;n.has(`f${o}`);)o++;const l=`f${o}`;return n.add(l),l};s.id=i();for(const o of s.fields??[])o.id=i();if(t.parent){this.snapshot(),t.list.splice(t.index+1,0,s),this.selected=s.id,this.markDirty(),this.renderCanvas(),this.renderInspector();return}this.insertField(s,t.index+1)}async deleteField(e){if(!this.schema)return;const t=this.locateField(e);if(!t)return;const s=this.schema.fields.filter(i=>i.logic?.rules?.some(o=>o.field===e)),n=s.length?`Delete this field? ${s.length} other field${s.length===1?"":"s"} use it in a condition, and those conditions will stop working.`:He("confirmDelete","Delete this? It cannot be undone.");await pe(n,"Delete field")&&(this.snapshot(),t.list.splice(t.index,1),this.selected===e&&(this.selected=null),this.markDirty(),this.renderCanvas(),this.renderInspector())}selectField(e){this.selected=e;for(const t of this.canvas.querySelectorAll("[data-atfb-card]")){const s=t.dataset.atfbCard===e;t.classList.toggle("is-selected",s),t.setAttribute("aria-pressed",s?"true":"false")}for(const t of this.canvas.querySelectorAll("[data-atfb-subfield]"))t.classList.toggle("is-selected",t.dataset.atfbSubfield===e);this.logicMap?.highlight(e),this.renderInspector()}nextFieldId(){const e=this.usedFieldIds();let t=e.size+1;for(;e.has(`f${t}`);)t++;return`f${t}`}usedFieldIds(){const e=new Set;for(const t of this.schema?.fields??[]){e.add(t.id);for(const s of t.fields??[])e.add(s.id)}return e}nextEntryId(e,t){const s=new Set(t.map(i=>i.id));let n=t.length+1;for(;s.has(`${e}${n}`);)n++;return`${e}${n}`}renderTypeSettings(e,t,s,n){for(const i of s){const o=$t[i];o&&(this.inspector.append(At(e,o,n)),o.also&&this.inspector.append(At(e,{...o,key:o.also.key,label:o.also.label,hint:o.also.hint},n)))}s.includes("rows")&&e.type!=="likert"&&this.inspector.append(m("Lines tall",G(String(e.rows??""),i=>n("rows",i)))),s.includes("rows")&&e.type==="likert"&&this.inspector.append(this.renderStatementList(e,n)),s.includes("parts")&&this.inspector.append(this.renderPartsEditor(e,t,n)),s.includes("default")&&!["files","object"].includes(t?.value??"")&&this.inspector.append(this.renderDefaultControl(e,t,n))}renderStatementList(e,t){const s=Array.isArray(e.rows)?e.rows:[];return m("Statements",he(s.map(n=>n.label??"").join(` -`),n=>t("rows",Ct(s,n)),5),"One per line. Each becomes a row of the matrix.")}renderPartsEditor(e,t,s){const n=t?.parts??[],i=Array.isArray(e.parts)?e.parts:n.map(l=>l.key),o=n.map(l=>D(l.label,i.includes(l.key),c=>{const d=n.map(h=>h.key).filter(h=>h===l.key?c:i.includes(h));s("parts",d.length?d:[l.key])}));return r("div",{class:"atfb-section",children:[r("h4",{class:"atfb-section__title",text:"Parts to ask for"}),...o]})}renderDefaultControl(e,t,s){const n="Filled in before they start. They can change it.";return e.type==="switch"||e.type==="consent"?D("On by default",!!e.default,i=>s("default",i)):t?.choices&&(e.choices??[]).length?m("Default answer",M(String(e.default??""),[{value:"",label:"Nothing chosen"},...(e.choices??[]).map(i=>({value:String(i.value),label:i.label||String(i.value)}))],i=>s("default",i)),n):m("Default answer",L(String(e.default??""),i=>s("default",i)),n)}renderChoicesEditor(e,t){const s=e.choices??[],n=o=>this.liveField(e.id)?.choices?.[o],i=r("div",{class:"atfb-choices"});return s.forEach((o,l)=>{const c=r("div",{class:"atfb-choice-row",children:[e.type==="image_choice"?this.choiceImageWell(o,l,e):null,J(L(o.label,d=>{const h=n(l);if(!h)return;const g=!h.value||h.value===h.label;h.label=d,g&&(h.value=d),this.markDirty();const v=this.liveField(e.id);v&&this.syncCanvas(v)}),`choice:${l}:label`),J(L(o.value,d=>{const h=n(l);h&&(h.value=d,this.markDirty())},"value"),`choice:${l}:value`),e.type==="quiz"||o.points!==void 0?G(String(o.points??""),d=>{const h=n(l);h&&(h.points=d===""?void 0:Number(d),this.markDirty())}):G(String(o.price??""),d=>{const h=n(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:[W("trash")]})]});i.append(c)}),r("div",{class:"atfb-section",children:[r("h4",{text:"Choices"}),r("p",{class:"atfb-hint",text:e.type==="quiz"?"Label, value, points.":"Label, value, and a price for calculations."}),i,E("Add choice",()=>{const o=this.liveField(e.id);if(!o)return;const l=o.choices??[];l.push({label:"",value:""}),t("choices",l),this.renderInspector()},"ghost","plus-alt2"),e.type==="quiz"?m("Correct answer",M(String(e.correct??""),[{value:"",label:"—"},...s.map(o=>({value:o.value,label:o.label}))],o=>t("correct",o))):null]})}choiceImageWell(e,t,s){const n=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}`}):W("format-image")]}),i=K().registerDropTarget({id:`atfb-well-${s.id}-${t}`,element:n,accept:o=>an.includes(o.type),onEnter:()=>n.classList.add("is-dropping"),onLeave:()=>n.classList.remove("is-dropping"),onDrop:o=>{n.classList.remove("is-dropping");const l=o.payload.data,c=Number(l.attachmentId??l.id??l.file?.id??0);if(!c){R("That is not an image this field can use","","error");return}const d=this.liveField(s.id)?.choices?.[t];d&&(d.image=c,this.markDirty(),this.renderInspector())}});return this.teardowns.push(i),n.addEventListener("click",()=>{const o=this.liveField(s.id)?.choices?.[t];o?.image&&(o.image=void 0,this.markDirty(),this.renderInspector())}),n}renderValidationSection(e,t,s){const n=[],i=[];t.includes("minlength")&&i.push(["minlength","Minimum characters"],["maxlength","Maximum characters"]),t.includes("min")?i.push(["min","Minimum"],["max","Maximum"]):t.includes("max")&&i.push(["max","Highest"]),t.includes("step")&&i.push(["step","Steps of"]),t.includes("mindate")&&i.push(["minDate","Earliest date"],["maxDate","Latest date"]),t.includes("mintime")&&i.push(["minTime","Earliest time"],["maxTime","Latest time"]);for(const[l,c]of i)n.push(m(c,L(String(e[l]??""),d=>s(l,d))));t.includes("pattern")&&n.push(...this.renderAnswerShapeRows(e,s)),t.includes("unique")&&n.push(D("No two people may submit the same value",!!e.unique,l=>s("unique",l)));const o=e.messages??{};return t.includes("required")&&n.push(m("Message when required",L(o.required??"",l=>{o.required=l,s("messages",o)}),"Leave empty for the default wording.")),n.length?this.section(`validation:${e.id}`,"Validation",n):r("div",{class:"atfb-section is-empty"})}buildShapePicker(e,t){const s=[{heading:null,options:[{value:"",label:"Anything at all"}]},...en.map(i=>({heading:i,options:_t.filter(o=>o.group===i).map(o=>({value:o.slug,label:o.label}))})),{heading:"Your own",options:[{value:"custom",label:"A custom rule…"}]}];if(z("os-select")&&z("os-option")){const i=document.createElement("os-select");i.setAttribute("value",e),i.setAttribute("aria-label","What the answer should look like"),i.classList.add("atfb-field");for(const o of s){if(o.heading){const l=document.createElement("os-option");l.setAttribute("value",`__heading:${o.heading}`),l.setAttribute("disabled",""),l.textContent=o.heading,i.append(l)}for(const l of o.options){const c=document.createElement("os-option");c.setAttribute("value",l.value),c.textContent=l.label,i.append(c)}}return i.addEventListener("os-pick",o=>{t(String(o.detail?.value??""))}),i}const n=r("select",{class:"atfb-input atfb-select",attrs:{"aria-label":"What the answer should look like"},on:{change:i=>t(i.target.value)}});for(const i of s){const o=i.heading?(()=>{const l=document.createElement("optgroup");return l.label=i.heading,n.append(l),l})():n;for(const l of i.options)o.append(r("option",{value:l.value,text:l.label,attrs:{selected:l.value===e}}))}return n}renderAnswerShapeRows(e,t){const n=(typeof e.validation=="string"?e.validation:"")||(e.pattern?"custom":""),i=()=>Vs({root:this.root,field:this.liveField(e.id)??e,onSave:h=>{t("validation","custom"),t("pattern",h.pattern),t("validationRecipe",JSON.stringify(h.recipe));const g={...this.liveField(e.id)?.messages??{}};g.invalid=h.message,t("messages",g),this.renderInspector()},onCancel:()=>this.renderInspector()}),o=h=>{if(h==="custom"){i();return}t("validation",h),t("pattern",""),t("validationRecipe",""),this.renderInspector()},l=this.buildShapePicker(n,o),c=tn(n),d=[m("The answer should be",l,c?`e.g. ${c.example}`:"Checked as they type, and again on the server.")];if(n==="custom"){const h=vt(String(e.validationRecipe??"")),g=yt(h)||(e.pattern?`Matches the expression ${String(e.pattern)}`:"No rule yet — open the builder.");d.push(m("Your rule",r("div",{class:"atfb-valrule",children:[r("p",{class:"atfb-valrule__words",text:g}),E("Edit the rule…",i,"secondary","edit")]}),ye(h)||e.pattern?"Built in the rule builder, with a playground to test it.":void 0))}return d}renderLogicSection(e){const t=e.logic,s=()=>this.liveField(e.id)?.logic,n=(d,h=!1)=>{const g=s();g&&(d(g),this.markDirty(),this.renderCanvas(),h&&this.renderInspector())},i=(this.schema?.fields??[]).filter(d=>d.id!==e.id&&d.type!=="page_break"),o=()=>r("div",{class:"atfb-rule-join",children:[r("button",{class:"atfb-rule-join__chip",type:"button",text:t.match==="all"?"and":"or",title:"Switch between needing every rule (and) or any one of them (or).",on:{click:()=>n(d=>{d.match=d.match==="all"?"any":"all"},!0)}})]}),l=(d,h)=>{const g=r("button",{class:"atfb-card__action",type:"button",attrs:{"aria-label":"Remove this rule"},title:"Remove this rule",on:{click:()=>n(p=>{p.rules.splice(h,1)},!0)},children:[W("trash")]}),v=[r("div",{class:"atfb-rule__top",children:[M(d.field,i.map(p=>({value:p.id,label:p.label||p.id})),p=>n(_=>{const y=_.rules[h];y&&(y.field=p,y.value="")},!0)),g]}),M(d.operator,Object.entries(this.config?.operators??{}).map(([p,_])=>({value:p,label:_})),p=>n(_=>{const y=_.rules[h];y&&(y.operator=p)},!0))];if(!Ae.includes(d.operator)){const p=this.schema?.fields.find(_=>_.id===d.field);if(p?.choices?.length){const _=p.choices.map(y=>({value:y.value,label:y.label||y.value}));d.value!==""&&!p.choices.some(y=>y.value===d.value)&&_.unshift({value:d.value,label:d.value}),v.push(M(d.value,_,y=>n(x=>{const F=x.rules[h];F&&(F.value=y)})))}else v.push(L(d.value,_=>n(y=>{const x=y.rules[h];x&&(x.value=_)}),"The answer to compare against"))}return r("div",{class:"atfb-rule",children:v})},c=r("div",{class:"atfb-rules"});return t.rules.forEach((d,h)=>{h>0&&c.append(o()),c.append(l(d,h))}),this.section(`logic:${e.id}`,"Conditional logic",[D("Only show this field sometimes",t.enabled,d=>{n(h=>{h.enabled=d},!0)}),t.enabled?r("div",{children:[r("div",{class:"atfb-rule-head",children:[M(t.action,[{value:"show",label:"Show"},{value:"hide",label:"Hide"}],d=>n(h=>{h.action=d},!0)),r("span",{text:"this field when"}),M(t.match,[{value:"all",label:"all"},{value:"any",label:"any"}],d=>n(h=>{h.match=d},!0)),r("span",{text:"of these match:"})]}),c,E("Add rule",()=>{n(d=>{d.rules.push({field:i[0]?.id??"",operator:"is",value:""})},!0)},"ghost","plus-alt2")]}):null],t.enabled)}renderTabCanvas(){return this.schema?this.tab==="theme"?ct({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 n=/