domma-cms 0.98.0 → 0.99.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
  /*!
2
- * Domma Tools CSS v0.45.0
2
+ * Domma Tools CSS v0.46.1
3
3
  * Dynamic Object Manipulation & Modeling API
4
4
  * (c) 2026 Darryl Waterhouse & DCBW-IT
5
- * Built: 2026-09-27T18:03:07.363Z
6
- * Commit: 224c5a0
5
+ * Built: 2026-09-27T20:43:29.160Z
6
+ * Commit: 92f2ee4
7
7
  */
8
8
 
9
9
  /*!
@@ -1,9 +1,9 @@
1
1
  /*!
2
- * Domma Tools v0.45.0
2
+ * Domma Tools v0.46.1
3
3
  * Developer tools: Theme Roller & Page Roller
4
4
  * (c) 2026 Darryl Waterhouse & DCBW-IT
5
- * Built: 2026-09-27T18:02:54.057Z
6
- * Commit: 224c5a0
5
+ * Built: 2026-09-27T20:43:15.062Z
6
+ * Commit: 92f2ee4
7
7
  *
8
8
  * Requires: domma.min.js
9
9
  */
package/admin/index.html CHANGED
@@ -80,7 +80,7 @@
80
80
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
81
81
 
82
82
  <!-- Form Logic Engine - needed for conditional logic preview -->
83
- <script src="/public/js/form-logic-engine.js?v=20260926-limits"></script>
83
+ <script src="/public/js/form-logic-engine.js?v=20260927-field-tokens"></script>
84
84
 
85
85
  <!-- Admin App (ES module entry) -->
86
86
  <script type="module" src="/admin/js/app.js"></script>
@@ -1,3 +1,3 @@
1
- export const FIELD_TYPES=[{value:"string",label:"Text (single line)"},{value:"email",label:"Email"},{value:"tel",label:"Phone"},{value:"number",label:"Number"},{value:"textarea",label:"Textarea (multi-line)"},{value:"select",label:"Dropdown (select)"},{value:"radio",label:"Radio buttons"},{value:"checkbox",label:"Single checkbox"},{value:"checkbox-group",label:"Checkbox group"},{value:"date",label:"Date"},{value:"time",label:"Time"},{value:"url",label:"URL"},{value:"hidden",label:"Hidden field"}],OPTION_TYPES=new Set(["select","radio","checkbox-group"]),OPERATIONS=["create","read","update","delete"],EXPORT_FORMATS=["csv","json"],FIELD_NAME_RE=/^[A-Za-z_][A-Za-z0-9_-]*$/;export function slugify(e){return String(e??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}export const nameFromLabel=e=>slugify(e).replace(/-/g,"_"),typeLabel=e=>FIELD_TYPES.find(n=>n.value===e)?.label||e||"";export function optionsText(e){return(Array.isArray(e)?e:[]).map(n=>typeof n=="string"?`${n}: ${n}`:`${n?.value??""}: ${n?.label??n?.value??""}`).join(`
1
+ export const FIELD_TYPES=[{value:"string",label:"Text (single line)"},{value:"email",label:"Email"},{value:"tel",label:"Phone"},{value:"number",label:"Number"},{value:"textarea",label:"Textarea (multi-line)"},{value:"select",label:"Dropdown (select)"},{value:"radio",label:"Radio buttons"},{value:"checkbox",label:"Single checkbox"},{value:"checkbox-group",label:"Checkbox group"},{value:"date",label:"Date"},{value:"time",label:"Time"},{value:"url",label:"URL"},{value:"hidden",label:"Hidden field"}],OPTION_TYPES=new Set(["select","radio","checkbox-group"]),ADDON_TYPES=new Set(["string","email","tel","number","textarea","select","date","time","url"]);const f=/^[a-z0-9-]+$/i,m=24;function p(e){return e?typeof e=="string"?{icon:"",text:e}:{icon:e.icon||"",text:e.icon?"":String(e.text??"")}:{icon:"",text:""}}export function addonFromBoxes(e,r){const n=String(e??"").trim(),t=String(r??"").trim();return n&&f.test(n)?{icon:n}:t?{text:t.slice(0,m)}:null}export const OPERATIONS=["create","read","update","delete"],EXPORT_FORMATS=["csv","json"],FIELD_NAME_RE=/^[A-Za-z_][A-Za-z0-9_-]*$/;export function slugify(e){return String(e??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}export const nameFromLabel=e=>slugify(e).replace(/-/g,"_"),typeLabel=e=>FIELD_TYPES.find(r=>r.value===e)?.label||e||"";export function optionsText(e){return(Array.isArray(e)?e:[]).map(r=>typeof r=="string"?`${r}: ${r}`:`${r?.value??""}: ${r?.label??r?.value??""}`).join(`
2
2
  `)}export function parseOptions(e){return String(e??"").split(`
3
- `).filter(n=>n.trim()).map(n=>{const[r,...t]=n.split(":");return{value:r.trim(),label:t.join(":").trim()||r.trim()}})}const d=()=>({create:{enabled:!1,access:"admin"},read:{enabled:!0,access:"public"},update:{enabled:!1,access:"admin"},delete:{enabled:!1,access:"admin"}}),m=e=>({enabled:!0,access:e||"",formats:["csv","json"]});export function roleLadder(e){return(Array.isArray(e)?e:e?.entries||[]).map(r=>({name:r?.data?.name,label:r?.data?.label||r?.data?.name,level:Number(r?.data?.level)})).filter(r=>typeof r.name=="string"&&r.name&&Number.isFinite(r.level)).sort((r,t)=>r.level-t.level||r.name.localeCompare(t.name))}export function accessOptions(e,n,{token:r=!0}={}){const t=[{value:"public",label:"Public - anyone"}];r&&t.push({value:"token",label:"API token"});for(const l of[...e||[]].reverse())t.push({value:l.name,label:l.level===0?`${l.label} only`:`${l.label} and above`});return typeof n=="string"&&n&&!t.some(l=>l.value===n)&&t.push(e?.length?{value:n,label:`${n} (role not found)`,missing:!0}:{value:n,label:n}),t}let f=0;export const fieldKey=()=>`f${Date.now().toString(36)}${(f++).toString(36)}`;export function formFromCollection(e=null,{project:n="",defaultRole:r=""}={}){const t=e||{},l={...d(),...t.api||{}},a={...m(r),...t.export||{}},s={title:String(t.title??""),slug:String(t.slug??""),description:String(t.description??""),layout:t.layout==="grid"?"grid":"stacked",columns:String(Number.parseInt(t.columns,10)||2),project:String(t.meta?.project??n??""),bundled:t.bundled===!0,storageAdapter:t.storage?.adapter==="mongodb"?"mongodb":"file",storageConnection:String(t.storage?.connection||"default"),fields:(Array.isArray(t.fields)?t.fields:[]).map(o=>({...o,_key:fieldKey()})),readFields:Array.isArray(l.read?.fields)?l.read.fields.join(", "):"",exportEnabled:a.enabled!==!1,exportAccess:typeof a.access=="string"&&a.access?a.access:String(r||""),exportCsv:(Array.isArray(a.formats)?a.formats:EXPORT_FORMATS).includes("csv"),exportJson:(Array.isArray(a.formats)?a.formats:EXPORT_FORMATS).includes("json")};for(const o of OPERATIONS){s[`${o}Enabled`]=l[o]?.enabled===!0;const i=l[o]?.access;s[`${o}Access`]=typeof i=="string"&&i?i:"admin"}return s}export function cleanField(e){const{_key:n,...r}=e||{};return r}export function payloadFromForm(e,{isNew:n=!1,meta:r={}}={}){const t=e||{},l={};for(const o of OPERATIONS)l[o]={enabled:t[`${o}Enabled`]===!0,access:t[`${o}Access`]||"admin"};const a=String(t.readFields??"").split(",").map(o=>o.trim()).filter(Boolean);a.length&&(l.read.fields=a);const s=Math.min(6,Math.max(1,Number.parseInt(t.columns,10)||2));return{title:String(t.title??"").trim(),...n&&{slug:slugify(t.slug||t.title)},description:String(t.description??"").trim(),layout:t.layout==="grid"?"grid":"stacked",columns:s,fields:(t.fields||[]).map(cleanField),api:l,export:{enabled:t.exportEnabled!==!1,...t.exportAccess&&{access:t.exportAccess},formats:EXPORT_FORMATS.filter(o=>o==="csv"?t.exportCsv:t.exportJson)},storage:t.storageAdapter==="mongodb"?{adapter:"mongodb",connection:t.storageConnection||"default"}:{adapter:"file"},meta:{...r||{},project:String(t.project??"")||null},bundled:t.bundled===!0}}export function snapshot(e){const{fields:n=[],...r}=e||{};return JSON.stringify({...r,fields:n.map(cleanField)})}export function checkForm(e,{isNew:n=!1,taken:r=[],original:t={}}={}){const l=e||{},a={};if(String(l.title??"").trim()||(a.title="A collection needs a title."),n){const c=slugify(l.slug||l.title);c?r.includes(c)&&(a.slug=`"${c}" is already a collection.`):a.slug="The slug needs a letter or a number."}const s=Number.parseInt(l.columns,10);l.layout==="grid"&&!(s>=1&&s<=6)&&(a.columns="Between 1 and 6 columns.");const o={},i=new Map;return(l.fields||[]).forEach(c=>{const p=fieldProblem(c,t[c._key]);p&&(o[c._key]=p);const u=String(c.name??"").trim();u&&(i.has(u)?o[c._key]=o[c._key]||`"${u}" is used by another field.`:i.set(u,c._key))}),Object.keys(o).length&&(a.fields=o),l.exportEnabled&&!l.exportCsv&&!l.exportJson&&(a.export="Pick at least one export format, or turn export off."),a}export function fieldProblem(e,n){const r=String(e?.name??"").trim();return r?r!==n&&!FIELD_NAME_RE.test(r)?"Names start with a letter or _, then letters, digits, _ or -.":OPTION_TYPES.has(e.type)&&!(Array.isArray(e.options)&&e.options.length)?`A ${typeLabel(e.type).toLowerCase()} needs at least one option.`:"":"A field needs a name - it is what each entry is keyed by."}export const errorCount=e=>Object.keys(e||{}).filter(n=>n!=="fields").length+Object.keys(e?.fields||{}).length;export function fieldRow(e,n={},{grid:r=!1}={}){const t=[e.name||"(no name)",typeLabel(e.type)];return OPTION_TYPES.has(e.type)&&t.push(`${(e.options||[]).length} options`),r&&t.push(e.fullWidth?"full width":`span ${e.span>1?e.span:1}`),{key:e._key,label:e.label||"(Untitled field)",meta:t.join(" \xB7 "),required:e.required===!0,hidden:e.type==="hidden",problem:n[e._key]||"",rowCls:n[e._key]?"is-bad":""}}export function moveField(e,n,r){const t=[...e];if(n<0||n>=t.length||r<0||r>=t.length||n===r)return t;const[l]=t.splice(n,1);return t.splice(r,0,l),t}export function duplicateField(e,n){const r=e[n];if(!r)return[...e];const t=new Set(e.map(i=>i.name)),l=`${r.name||"field"}_copy`;let a=l;for(let i=2;t.has(a);i++)a=`${l}_${i}`;const s={...JSON.parse(JSON.stringify(cleanField(r))),name:a,label:`${r.label||"Field"} (copy)`,_key:fieldKey()};delete s.id;const o=[...e];return o.splice(n+1,0,s),o}export const newField=()=>({id:`field-${Date.now()}`,name:"",label:"",type:"string",required:!1,placeholder:"",helper:"",options:[],validation:[],logic:null,_key:fieldKey()});export function panelForm(e){return{label:String(e.label??""),name:String(e.name??""),type:e.type||"string",required:e.required===!0,placeholder:String(e.placeholder??""),helper:String(e.helper??""),options:optionsText(e.options),span:String(e.span>1?e.span:1),fullWidth:e.fullWidth===!0}}export function fieldFromPanel(e,n){const r={...e,label:String(n.label??"").trim(),name:String(n.name??"").trim(),type:n.type||"string",required:n.required===!0,placeholder:String(n.placeholder??"").trim(),helper:String(n.helper??"").trim()};if(OPTION_TYPES.has(r.type)&&(r.options=parseOptions(n.options)),n.fullWidth)r.fullWidth=!0,delete r.span;else{delete r.fullWidth;const t=Number.parseInt(n.span,10);t>1?r.span=Math.min(6,t):delete r.span}return r}export function storageChange(e,n){const r=e||"file",t=n||"file";return r===t?"":`${r} \u2192 ${t}`}export function draftOf(e){const{fields:n=[],...r}=e||{};return{...r,fields:n.map(cleanField)}}export function formFromDraft(e,n){const r=e||{};return{...n,...r,fields:(Array.isArray(r.fields)?r.fields:n.fields).map(t=>({...cleanField(t),_key:fieldKey()}))}}
3
+ `).filter(r=>r.trim()).map(r=>{const[n,...t]=r.split(":");return{value:n.trim(),label:t.join(":").trim()||n.trim()}})}const g=()=>({create:{enabled:!1,access:"admin"},read:{enabled:!0,access:"public"},update:{enabled:!1,access:"admin"},delete:{enabled:!1,access:"admin"}}),b=e=>({enabled:!0,access:e||"",formats:["csv","json"]});export function roleLadder(e){return(Array.isArray(e)?e:e?.entries||[]).map(n=>({name:n?.data?.name,label:n?.data?.label||n?.data?.name,level:Number(n?.data?.level)})).filter(n=>typeof n.name=="string"&&n.name&&Number.isFinite(n.level)).sort((n,t)=>n.level-t.level||n.name.localeCompare(t.name))}export function accessOptions(e,r,{token:n=!0}={}){const t=[{value:"public",label:"Public - anyone"}];n&&t.push({value:"token",label:"API token"});for(const l of[...e||[]].reverse())t.push({value:l.name,label:l.level===0?`${l.label} only`:`${l.label} and above`});return typeof r=="string"&&r&&!t.some(l=>l.value===r)&&t.push(e?.length?{value:r,label:`${r} (role not found)`,missing:!0}:{value:r,label:r}),t}let x=0;export const fieldKey=()=>`f${Date.now().toString(36)}${(x++).toString(36)}`;export function formFromCollection(e=null,{project:r="",defaultRole:n=""}={}){const t=e||{},l={...g(),...t.api||{}},o={...b(n),...t.export||{}},a={title:String(t.title??""),slug:String(t.slug??""),description:String(t.description??""),layout:t.layout==="grid"?"grid":"stacked",columns:String(Number.parseInt(t.columns,10)||2),project:String(t.meta?.project??r??""),bundled:t.bundled===!0,storageAdapter:t.storage?.adapter==="mongodb"?"mongodb":"file",storageConnection:String(t.storage?.connection||"default"),fields:(Array.isArray(t.fields)?t.fields:[]).map(i=>({...i,_key:fieldKey()})),readFields:Array.isArray(l.read?.fields)?l.read.fields.join(", "):"",exportEnabled:o.enabled!==!1,exportAccess:typeof o.access=="string"&&o.access?o.access:String(n||""),exportCsv:(Array.isArray(o.formats)?o.formats:EXPORT_FORMATS).includes("csv"),exportJson:(Array.isArray(o.formats)?o.formats:EXPORT_FORMATS).includes("json")};for(const i of OPERATIONS){a[`${i}Enabled`]=l[i]?.enabled===!0;const s=l[i]?.access;a[`${i}Access`]=typeof s=="string"&&s?s:"admin"}return a}export function cleanField(e){const{_key:r,...n}=e||{};return n}export function payloadFromForm(e,{isNew:r=!1,meta:n={}}={}){const t=e||{},l={};for(const i of OPERATIONS)l[i]={enabled:t[`${i}Enabled`]===!0,access:t[`${i}Access`]||"admin"};const o=String(t.readFields??"").split(",").map(i=>i.trim()).filter(Boolean);o.length&&(l.read.fields=o);const a=Math.min(6,Math.max(1,Number.parseInt(t.columns,10)||2));return{title:String(t.title??"").trim(),...r&&{slug:slugify(t.slug||t.title)},description:String(t.description??"").trim(),layout:t.layout==="grid"?"grid":"stacked",columns:a,fields:(t.fields||[]).map(cleanField),api:l,export:{enabled:t.exportEnabled!==!1,...t.exportAccess&&{access:t.exportAccess},formats:EXPORT_FORMATS.filter(i=>i==="csv"?t.exportCsv:t.exportJson)},storage:t.storageAdapter==="mongodb"?{adapter:"mongodb",connection:t.storageConnection||"default"}:{adapter:"file"},meta:{...n||{},project:String(t.project??"")||null},bundled:t.bundled===!0}}export function snapshot(e){const{fields:r=[],...n}=e||{};return JSON.stringify({...n,fields:r.map(cleanField)})}export function checkForm(e,{isNew:r=!1,taken:n=[],original:t={}}={}){const l=e||{},o={};if(String(l.title??"").trim()||(o.title="A collection needs a title."),r){const c=slugify(l.slug||l.title);c?n.includes(c)&&(o.slug=`"${c}" is already a collection.`):o.slug="The slug needs a letter or a number."}const a=Number.parseInt(l.columns,10);l.layout==="grid"&&!(a>=1&&a<=6)&&(o.columns="Between 1 and 6 columns.");const i={},s=new Map;return(l.fields||[]).forEach(c=>{const d=fieldProblem(c,t[c._key]);d&&(i[c._key]=d);const u=String(c.name??"").trim();u&&(s.has(u)?i[c._key]=i[c._key]||`"${u}" is used by another field.`:s.set(u,c._key))}),Object.keys(i).length&&(o.fields=i),l.exportEnabled&&!l.exportCsv&&!l.exportJson&&(o.export="Pick at least one export format, or turn export off."),o}export function fieldProblem(e,r){const n=String(e?.name??"").trim();return n?n!==r&&!FIELD_NAME_RE.test(n)?"Names start with a letter or _, then letters, digits, _ or -.":OPTION_TYPES.has(e.type)&&!(Array.isArray(e.options)&&e.options.length)?`A ${typeLabel(e.type).toLowerCase()} needs at least one option.`:"":"A field needs a name - it is what each entry is keyed by."}export const errorCount=e=>Object.keys(e||{}).filter(r=>r!=="fields").length+Object.keys(e?.fields||{}).length;export function fieldRow(e,r={},{grid:n=!1}={}){const t=[e.name||"(no name)",typeLabel(e.type)];return OPTION_TYPES.has(e.type)&&t.push(`${(e.options||[]).length} options`),n&&t.push(e.fullWidth?"full width":`span ${e.span>1?e.span:1}`),{key:e._key,label:e.label||"(Untitled field)",meta:t.join(" \xB7 "),required:e.required===!0,hidden:e.type==="hidden",problem:r[e._key]||"",rowCls:r[e._key]?"is-bad":""}}export function moveField(e,r,n){const t=[...e];if(r<0||r>=t.length||n<0||n>=t.length||r===n)return t;const[l]=t.splice(r,1);return t.splice(n,0,l),t}export function duplicateField(e,r){const n=e[r];if(!n)return[...e];const t=new Set(e.map(s=>s.name)),l=`${n.name||"field"}_copy`;let o=l;for(let s=2;t.has(o);s++)o=`${l}_${s}`;const a={...JSON.parse(JSON.stringify(cleanField(n))),name:o,label:`${n.label||"Field"} (copy)`,_key:fieldKey()};delete a.id;const i=[...e];return i.splice(r+1,0,a),i}export const newField=()=>({id:`field-${Date.now()}`,name:"",label:"",type:"string",required:!1,placeholder:"",helper:"",options:[],validation:[],logic:null,_key:fieldKey()});export function panelForm(e){return{label:String(e.label??""),name:String(e.name??""),type:e.type||"string",required:e.required===!0,placeholder:String(e.placeholder??""),helper:String(e.helper??""),options:optionsText(e.options),span:String(e.span>1?e.span:1),fullWidth:e.fullWidth===!0,prefixIcon:p(e.prefix).icon,prefixText:p(e.prefix).text,suffixIcon:p(e.suffix).icon,suffixText:p(e.suffix).text}}export function fieldFromPanel(e,r){const n={...e,label:String(r.label??"").trim(),name:String(r.name??"").trim(),type:r.type||"string",required:r.required===!0,placeholder:String(r.placeholder??"").trim(),helper:String(r.helper??"").trim()};OPTION_TYPES.has(n.type)&&(n.options=parseOptions(r.options));for(const t of["prefix","suffix"]){const l=`${t}Icon`in r||`${t}Text`in r;if(!ADDON_TYPES.has(n.type)){delete n[t];continue}if(!l)continue;const o=addonFromBoxes(r[`${t}Icon`],r[`${t}Text`]);o?n[t]=o:delete n[t]}if(r.fullWidth)n.fullWidth=!0,delete n.span;else{delete n.fullWidth;const t=Number.parseInt(r.span,10);t>1?n.span=Math.min(6,t):delete n.span}return n}export function storageChange(e,r){const n=e||"file",t=r||"file";return n===t?"":`${n} \u2192 ${t}`}export function draftOf(e){const{fields:r=[],...n}=e||{};return{...n,fields:r.map(cleanField)}}export function formFromDraft(e,r){const n=e||{};return{...r,...n,fields:(Array.isArray(n.fields)?n.fields:r.fields).map(t=>({...cleanField(t),_key:fieldKey()}))}}
@@ -194,6 +194,9 @@
194
194
  .cex-list { display: flex; flex-direction: column; }
195
195
  .cex-row { display: flex; align-items: center; gap: .6rem; padding: .5rem .35rem; border-bottom: 1px solid var(--dm-border); cursor: pointer; }
196
196
  .cex-row:last-child { border-bottom: 0; }
197
+ .cex-addon-row { display: flex; align-items: center; gap: .4rem; margin-top: .35rem; }
198
+ .cex-addon-row > .form-input { flex: 1; min-width: 0; }
199
+ .cex-addon-side { flex: none; width: 3.2rem; font-size: var(--dm-text-sm, .85rem); color: var(--dm-text-muted); }
197
200
  .cex-row:hover, .cex-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
198
201
  .cex-row.is-bad { box-shadow: inset 3px 0 0 var(--dm-danger); }
199
202
  /* Six dots drawn in CSS: the row template is a data-each template, so an icon
@@ -226,6 +226,11 @@
226
226
  stored. <code>notify</code> exists only on the server (notification source
227
227
  <code>core:form-triggers</code>).</li>
228
228
  </ul>
229
+ <p>Messages that the visitor sees (banner, toast, block reason, end message and done message) fill
230
+ <code>&#123;&#123;field_name&#125;&#125;</code> tokens with the posted answers - option labels for choices, nothing for
231
+ passwords, files, images and any field a trigger hid, disabled or masked - so the <code>400</code> body and the
232
+ done message match what the browser showed. Values are plain text, one line, at most 200 characters.
233
+ Redirect URLs and notify titles are not filled.</p>
229
234
 
230
235
  <h2>Submissions</h2>
231
236
  <p>All submissions routes read and write the form's target collection, and only this form's entries in it (in a
@@ -64,6 +64,9 @@ Subject Prefix: [Contact Form]</code></pre>
64
64
  because MongoDB is not connected - the submission is still stored.</p>
65
65
  <p>A field's <strong>triggers</strong> (Fields tab) can also run an action, notify the admins or redirect
66
66
  after submit, only when the answers meet the trigger's condition.</p>
67
+ <p>A trigger's messages can use the visitor's answers: a banner reading
68
+ <code>Hello &#123;&#123;name&#125;&#125;</code> greets them by name as they type. Use <strong>Insert field</strong> beside the
69
+ message box to pick a field. Passwords, files and images are never repeated back.</p>
67
70
 
68
71
  <h3>4. Success message vs. redirect</h3>
69
72
  <p>After a successful submission the visitor sees one of two things:</p>
@@ -21,7 +21,10 @@
21
21
  number, textarea, dropdown, radio buttons, single checkbox, checkbox group, date, time,
22
22
  URL and hidden fields. Reference (a link to an entry in another collection), file and
23
23
  multi-select fields exist too, but are added by editing the collection's
24
- <code>schema.json</code>.</li>
24
+ <code>schema.json</code>. Click a field to edit it; under <strong>Joined to the
25
+ input</strong> a text, number, dropdown, date, time, URL or textarea field can have an icon
26
+ or a short text (such as <code>£</code> or <code>kg</code>) joined to the start or end of
27
+ its input in the entry form.</li>
25
28
  <li><strong>API &amp; Export</strong> - what the public API may do (read, create, update,
26
29
  delete) and who may do it, which fields a read returns, and whether visitors may export
27
30
  what a <code>[collection]</code> display shows.</li>
@@ -328,6 +328,10 @@
328
328
  .fb-trig-p-fields { background-image: none; height: auto; min-height: 6rem; padding: .3rem; }
329
329
  .fb-trig-f { display: flex; flex-direction: column; gap: .3rem; min-width: 0; margin: 0; }
330
330
  .fb-trig-f--full { grid-column: 1 / -1; }
331
+ /* A trigger message box and its "Insert field" picker ({{field_name}} tokens). */
332
+ .fb-token-row { display: flex; gap: .4rem; min-width: 0; }
333
+ .fb-token-row > .form-input:first-child { flex: 1; min-width: 0; }
334
+ .fb-token-pick { flex: none; width: auto; max-width: 12rem; }
331
335
  .fb-trig-f-label { font-size: var(--dm-text-xs); font-weight: 600; color: var(--dm-text-muted); }
332
336
  .fb-trig-f-hint { font-size: var(--dm-text-xs); color: var(--dm-text-muted); }
333
337
  .fb-trig-opts { display: flex; flex-wrap: wrap; gap: .5rem 1.25rem; padding-top: .65rem; border-top: 1px dashed var(--dm-border); }
@@ -1,11 +1,11 @@
1
- import{api as g}from"../api.js";import{addBannerAction as de}from"../lib/plugin-chrome.js";import{helpPopovers as Ae}from"../lib/help-popover.js";import{onceClosed as lt,openMenuAt as Fe}from"../lib/tool-kit.js";import{FIELD_TYPES as st,OPERATIONS as it,OPTION_TYPES as rt,accessOptions as ct,checkForm as dt,draftOf as ut,duplicateField as pt,errorCount as B,fieldFromPanel as Ne,fieldProblem as ft,fieldRow as mt,formFromCollection as De,formFromDraft as vt,moveField as Le,nameFromLabel as ht,newField as bt,panelForm as gt,payloadFromForm as yt,roleLadder as wt,slugify as X,snapshot as _,storageChange as Te}from"../lib/collection-editor-arrange.js";import{fmtDateTime as xt}from"../lib/dates.js";import{rowPlaceKeeper as kt}from"../lib/menu-drag.js";const Et=/^\/collections\/(new|edit\/[^/?#]+)\/?(\?.*)?$/,St=s=>`collection-editor.draft.${s||"new"}`,Mt=`
1
+ import{api as g}from"../api.js";import{addBannerAction as de}from"../lib/plugin-chrome.js";import{helpPopovers as Ie}from"../lib/help-popover.js";import{onceClosed as st,openMenuAt as Re}from"../lib/tool-kit.js";import{ADDON_TYPES as it,FIELD_TYPES as rt,OPERATIONS as ct,OPTION_TYPES as dt,accessOptions as pt,checkForm as ut,draftOf as ft,duplicateField as mt,errorCount as q,fieldFromPanel as Te,fieldProblem as ht,fieldRow as vt,formFromCollection as Fe,formFromDraft as bt,moveField as De,nameFromLabel as gt,newField as yt,panelForm as xt,payloadFromForm as wt,roleLadder as kt,slugify as X,snapshot as _,storageChange as Ne}from"../lib/collection-editor-arrange.js";import{fmtDateTime as Et}from"../lib/dates.js";import{rowPlaceKeeper as St}from"../lib/menu-drag.js";import{openIconPicker as Pe}from"../lib/card-builder.js";const Mt=/^\/collections\/(new|edit\/[^/?#]+)\/?(\?.*)?$/,$t=s=>`collection-editor.draft.${s||"new"}`,Ct=`
2
2
  <div class="cex-row" data-key="{{key}}" tabindex="0" data-bind-class="rowCls">
3
3
  <span class="cex-grip" title="Drag to reorder" aria-hidden="true"></span>
4
4
  <span class="cex-main"><strong data-bind-text="label"></strong><small data-bind-text="meta"></small><small class="cex-problem" data-bind-hidden="!problem" data-bind-text="problem"></small></span>
5
5
  <span class="cex-chip is-ok" data-bind-hidden="!required">Required</span>
6
6
  <span class="cex-chip" data-bind-hidden="!hidden">Hidden</span>
7
7
  <button type="button" class="cex-kebab" aria-label="Actions for this field" title="Actions">\u22EE</button>
8
- </div>`,Z=s=>String(s??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");function $t(s){const d=document.createElement("template");return d.innerHTML=s.trim(),d.content.firstElementChild}function Pe(s,d){const i=s.querySelector(".dm-plugin-banner-name");if(!i)return;const C=document.createElement("span");C.className="dm-plugin-banner-parent",C.textContent="Collections";const w=document.createElement("span");w.className="dm-plugin-banner-sep",w.setAttribute("aria-hidden","true"),w.textContent="\u203A",i.replaceChildren(C,w,document.createTextNode(d))}let y=null,qe=!1;const ee=s=>String(s||"").split("?")[0];function Ct(){qe||!window.R?.use||(qe=!0,R.use((s,d,i)=>{if(!(y&&Et.test(d?.path||"")&&ee(s?.path)!==ee(d?.path))||!y.dirty())return i();const w=`#${d.path}`;return E.confirm("This collection has unsaved changes. Leave anyway? They are kept as a draft for your next visit.").then(c=>{c?i():history.replaceState(null,"",w)})}),M.subscribe("router:afterChange",({to:s}={})=>{if(y&&ee(s?.path)!==y.path){const d=y;y=null,d.dispose()}}))}export const collectionEditorView={templateUrl:"/admin/js/templates/collection-editor.html",async onMount(s){if(Ct(),y){const e=y;y=null,e.dispose()}const d=s.get(0),i=d.querySelector(".cex")||d,C=ee(location.hash.replace(/^#/,""))||"/collections/new",w=C.match(/^\/collections\/edit\/([^/?#]+)/),c=w?decodeURIComponent(w[1]):null,D=!c,ue=[],h=e=>(ue.push(e),e);let pe=!1;const te=()=>!pe&&i.isConnected,fe=E.loader(d,{type:"dots"});let O=null,ne=[],L=null,me=[],u=0,T=[];try{[O,ne,L,me,T]=await Promise.all([c?g.collections.get(c):Promise.resolve(null),g.projects.list().catch(()=>[]),g.collections.proStatus().catch(()=>null),D?g.collections.list().then(e=>(e||[]).map(t=>t.slug)).catch(()=>[]):Promise.resolve([]),g.collections.listEntries("roles",{limit:1e3}).then(wt).catch(()=>[])]),c&&(u=(await g.collections.listEntries(c,{limit:1}).catch(()=>null))?.total??0)}catch(e){fe.destroy(),E.toast(e?.status===404?"Collection not found.":`Could not load the collection: ${e.message||e}`,{type:"error"}),R.navigate("/collections");return}if(fe.destroy(),!i.isConnected)return;if(c&&!O){E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const ve=new URLSearchParams(location.hash.split("?")[1]||"").get("project")||"",f=De(O,{project:ve,defaultRole:T.at(-1)?.name||""}),x=Object.fromEntries(f.fields.map(e=>[e._key,e.name])),Ie=O?.meta||{};let K=f.storageAdapter;const Be=new Set(["public","token",...T.map(e=>e.name)]),he=(e,t)=>{if(!T.length)return"";const o=[...new Set(e.filter(a=>a&&!Be.has(a)))];return o.length?`Not a role on this site: ${o.join(", ")}. Until you pick another, ${t}.`:""},n={};for(const[e,t]of Object.entries(f))n[e]=M.observable(t);const k=M.observable(D),U=M.observable(!1),H=()=>Object.fromEntries(Object.keys(n).map(e=>[e,n[e].peek()])),be=()=>Object.fromEntries(Object.keys(n).map(e=>[e,n[e].value])),N=M.observable(_(f)),P=M.computed(()=>_(be())!==N.value);let W=!D;h(M.effect(()=>{const e=n.title.value;W||(n.slug.value=X(e))}));const m=M.computed(()=>dt(be(),{isNew:k.value,taken:me,original:x})),ge=M.observable([]);h(M.effect(()=>{const e=m.value.fields||{},t=n.layout.value==="grid";ge.value=n.fields.value.map(o=>mt(o,e,{grid:t}))}));const _e=M.computed(()=>new Set(n.fields.value.map(e=>e.name))),Ke={...n,isNew:k,saving:U,dirty:P,rows:ge,errTitle:M.computed(()=>m.value.title||""),errSlug:M.computed(()=>m.value.slug||""),errColumns:M.computed(()=>m.value.columns||""),errExport:M.computed(()=>m.value.export||""),problemCount:M.computed(()=>B(m.value)),problemText:M.computed(()=>{const e=B(m.value);return e?`${e} thing${e===1?"":"s"} to fix before it can be saved`:""}),settingsBad:M.computed(()=>!!(m.value.title||m.value.slug||m.value.columns)),fieldsBad:M.computed(()=>!!m.value.fields),exportBad:M.computed(()=>!!m.value.export),fieldCount:M.computed(()=>n.fields.value.length),slugHint:M.computed(()=>k.value?`Will be created as "${X(n.slug.value||n.title.value)||"\u2026"}".`:"Cannot be changed once the collection exists."),apiPath:M.computed(()=>`/api/collections/${X(n.slug.value||n.title.value)||"<slug>"}/public`),unknownReadFields:M.computed(()=>{const e=String(n.readFields.value||"").split(",").map(t=>t.trim()).filter(t=>t&&!_e.value.has(t));return e.length?`Not a field of this collection: ${e.join(", ")}`:""}),missingApiRoles:M.computed(()=>he(it.filter(e=>n[`${e}Enabled`].value).map(e=>n[`${e}Access`].value),"only the level-0 role passes")),missingExportRole:M.computed(()=>he(n.exportEnabled.value?[n.exportAccess.value]:[],"only the level-0 role is offered export")),showStorage:M.observable(!!L?.pro&&c!=="roles"),storageNote:M.computed(()=>{const e=Te(K,n.storageAdapter.value);return!e||k.value?"":u?`Switching ${e}: Save asks whether to move the ${u} existing ${u===1?"entry":"entries"} across. Export them first if they matter.`:`Switching ${e}. There are no entries to move.`})},ye=i.querySelector("#collection-project");for(const e of ne){const t=document.createElement("option");t.value=e.slug,t.textContent=e.name||e.slug,ye.appendChild(t)}if(f.project&&!ne.some(e=>e.slug===f.project)){const e=document.createElement("option");e.value=f.project,e.textContent=`${f.project} (not found)`,ye.appendChild(e)}const Ue=[...i.querySelectorAll('.cex-op select[data-model$="Access.value"]')],we=e=>{for(const t of Ue){const o=t.getAttribute("data-model").split(".")[0],a=e[o];t.replaceChildren(...ct(T,a,{token:t.id!=="export-access"}).map(r=>new Option(r.label,r.value))),t.value=a}};we(f);const He=i.querySelector("#storage-connection"),We=Array.isArray(L?.connections)&&L.connections.length?L.connections:["default"];for(const e of new Set([...We,f.storageConnection])){const t=document.createElement("option");t.value=e,t.textContent=e,He.appendChild(t)}const Y=e=>n.fields.peek().findIndex(t=>t._key===e),j=e=>{n.fields.value=e};function z(e,t){const o=Y(e);o<0||(j(Le(n.fields.peek(),o,o+t)),requestAnimationFrame(()=>i.querySelector(`.cex-row[data-key="${e}"]`)?.focus()))}async function oe(e){const t=Y(e);if(t<0)return;const o=n.fields.peek()[t];!k.peek()&&u&&x[e]&&!await E.confirm(`Remove the field "${Z(o.label||o.name)}"? The ${u} existing ${u===1?"entry keeps its":"entries keep their"} value for it, but it is no longer shown or edited. Nothing is lost until you Save - Discard puts it back.`)||j(n.fields.peek().filter(a=>a._key!==e))}function G(e,{isAdded:t=!1}={}){const o=Y(e);if(o<0)return;const a=n.fields.peek()[o],r=gt(a),v={};for(const[l,$]of Object.entries(r))v[l]=M.observable($);const p=JSON.stringify(r),J=()=>Object.fromEntries(Object.keys(v).map(l=>[l,v[l].peek()])),$e=()=>JSON.stringify(J())!==p;let Ce=!!a.name;const Xe=M.effect(()=>{const l=v.label.value;Ce||(v.name.value=ht(l))}),Ze=new Set(n.fields.peek().filter(l=>l._key!==e).map(l=>l.name)),Oe=M.computed(()=>{const l=Ne(a,Object.fromEntries(Object.keys(v).map($=>[$,v[$].value])));return l.name&&Ze.has(l.name)?`"${l.name}" is used by another field.`:ft(l,x[e])}),et=M.computed(()=>!k.peek()&&u&&x[e]&&v.name.value.trim()!==x[e]?`Entries keep their value under "${x[e]}" - renaming the field does not move it.`:""),tt={...v,problem:Oe,renamed:et,isOptions:M.computed(()=>rt.has(v.type.value)),isGrid:M.computed(()=>n.layout.value==="grid")},b=E.slideover({title:t?"New field":`Field: ${Z(a.label||a.name||"Untitled")}`,size:"md",position:"right"}),F=$t(`
8
+ </div>`,Z=s=>String(s??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");function Ot(s){const p=document.createElement("template");return p.innerHTML=s.trim(),p.content.firstElementChild}function Be(s,p){const i=s.querySelector(".dm-plugin-banner-name");if(!i)return;const C=document.createElement("span");C.className="dm-plugin-banner-parent",C.textContent="Collections";const x=document.createElement("span");x.className="dm-plugin-banner-sep",x.setAttribute("aria-hidden","true"),x.textContent="\u203A",i.replaceChildren(C,x,document.createTextNode(p))}let y=null,Le=!1;const ee=s=>String(s||"").split("?")[0];function jt(){Le||!window.R?.use||(Le=!0,R.use((s,p,i)=>{if(!(y&&Mt.test(p?.path||"")&&ee(s?.path)!==ee(p?.path))||!y.dirty())return i();const x=`#${p.path}`;return E.confirm("This collection has unsaved changes. Leave anyway? They are kept as a draft for your next visit.").then(d=>{d?i():history.replaceState(null,"",x)})}),M.subscribe("router:afterChange",({to:s}={})=>{if(y&&ee(s?.path)!==y.path){const p=y;y=null,p.dispose()}}))}export const collectionEditorView={templateUrl:"/admin/js/templates/collection-editor.html",async onMount(s){if(jt(),y){const e=y;y=null,e.dispose()}const p=s.get(0),i=p.querySelector(".cex")||p,C=ee(location.hash.replace(/^#/,""))||"/collections/new",x=C.match(/^\/collections\/edit\/([^/?#]+)/),d=x?decodeURIComponent(x[1]):null,D=!d,pe=[],v=e=>(pe.push(e),e);let ue=!1;const te=()=>!ue&&i.isConnected,fe=E.loader(p,{type:"dots"});let O=null,ne=[],N=null,me=[],u=0,P=[];try{[O,ne,N,me,P]=await Promise.all([d?g.collections.get(d):Promise.resolve(null),g.projects.list().catch(()=>[]),g.collections.proStatus().catch(()=>null),D?g.collections.list().then(e=>(e||[]).map(t=>t.slug)).catch(()=>[]):Promise.resolve([]),g.collections.listEntries("roles",{limit:1e3}).then(kt).catch(()=>[])]),d&&(u=(await g.collections.listEntries(d,{limit:1}).catch(()=>null))?.total??0)}catch(e){fe.destroy(),E.toast(e?.status===404?"Collection not found.":`Could not load the collection: ${e.message||e}`,{type:"error"}),R.navigate("/collections");return}if(fe.destroy(),!i.isConnected)return;if(d&&!O){E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const he=new URLSearchParams(location.hash.split("?")[1]||"").get("project")||"",m=Fe(O,{project:he,defaultRole:P.at(-1)?.name||""}),w=Object.fromEntries(m.fields.map(e=>[e._key,e.name])),qe=O?.meta||{};let K=m.storageAdapter;const _e=new Set(["public","token",...P.map(e=>e.name)]),ve=(e,t)=>{if(!P.length)return"";const o=[...new Set(e.filter(l=>l&&!_e.has(l)))];return o.length?`Not a role on this site: ${o.join(", ")}. Until you pick another, ${t}.`:""},n={};for(const[e,t]of Object.entries(m))n[e]=M.observable(t);const k=M.observable(D),U=M.observable(!1),H=()=>Object.fromEntries(Object.keys(n).map(e=>[e,n[e].peek()])),be=()=>Object.fromEntries(Object.keys(n).map(e=>[e,n[e].value])),F=M.observable(_(m)),B=M.computed(()=>_(be())!==F.value);let W=!D;v(M.effect(()=>{const e=n.title.value;W||(n.slug.value=X(e))}));const h=M.computed(()=>ut(be(),{isNew:k.value,taken:me,original:w})),ge=M.observable([]);v(M.effect(()=>{const e=h.value.fields||{},t=n.layout.value==="grid";ge.value=n.fields.value.map(o=>vt(o,e,{grid:t}))}));const Ke=M.computed(()=>new Set(n.fields.value.map(e=>e.name))),Ue={...n,isNew:k,saving:U,dirty:B,rows:ge,errTitle:M.computed(()=>h.value.title||""),errSlug:M.computed(()=>h.value.slug||""),errColumns:M.computed(()=>h.value.columns||""),errExport:M.computed(()=>h.value.export||""),problemCount:M.computed(()=>q(h.value)),problemText:M.computed(()=>{const e=q(h.value);return e?`${e} thing${e===1?"":"s"} to fix before it can be saved`:""}),settingsBad:M.computed(()=>!!(h.value.title||h.value.slug||h.value.columns)),fieldsBad:M.computed(()=>!!h.value.fields),exportBad:M.computed(()=>!!h.value.export),fieldCount:M.computed(()=>n.fields.value.length),slugHint:M.computed(()=>k.value?`Will be created as "${X(n.slug.value||n.title.value)||"\u2026"}".`:"Cannot be changed once the collection exists."),apiPath:M.computed(()=>`/api/collections/${X(n.slug.value||n.title.value)||"<slug>"}/public`),unknownReadFields:M.computed(()=>{const e=String(n.readFields.value||"").split(",").map(t=>t.trim()).filter(t=>t&&!Ke.value.has(t));return e.length?`Not a field of this collection: ${e.join(", ")}`:""}),missingApiRoles:M.computed(()=>ve(ct.filter(e=>n[`${e}Enabled`].value).map(e=>n[`${e}Access`].value),"only the level-0 role passes")),missingExportRole:M.computed(()=>ve(n.exportEnabled.value?[n.exportAccess.value]:[],"only the level-0 role is offered export")),showStorage:M.observable(!!N?.pro&&d!=="roles"),storageNote:M.computed(()=>{const e=Ne(K,n.storageAdapter.value);return!e||k.value?"":u?`Switching ${e}: Save asks whether to move the ${u} existing ${u===1?"entry":"entries"} across. Export them first if they matter.`:`Switching ${e}. There are no entries to move.`})},ye=i.querySelector("#collection-project");for(const e of ne){const t=document.createElement("option");t.value=e.slug,t.textContent=e.name||e.slug,ye.appendChild(t)}if(m.project&&!ne.some(e=>e.slug===m.project)){const e=document.createElement("option");e.value=m.project,e.textContent=`${m.project} (not found)`,ye.appendChild(e)}const He=[...i.querySelectorAll('.cex-op select[data-model$="Access.value"]')],xe=e=>{for(const t of He){const o=t.getAttribute("data-model").split(".")[0],l=e[o];t.replaceChildren(...pt(P,l,{token:t.id!=="export-access"}).map(r=>new Option(r.label,r.value))),t.value=l}};xe(m);const We=i.querySelector("#storage-connection"),Ye=Array.isArray(N?.connections)&&N.connections.length?N.connections:["default"];for(const e of new Set([...Ye,m.storageConnection])){const t=document.createElement("option");t.value=e,t.textContent=e,We.appendChild(t)}const Y=e=>n.fields.peek().findIndex(t=>t._key===e),j=e=>{n.fields.value=e};function z(e,t){const o=Y(e);o<0||(j(De(n.fields.peek(),o,o+t)),requestAnimationFrame(()=>i.querySelector(`.cex-row[data-key="${e}"]`)?.focus()))}async function oe(e){const t=Y(e);if(t<0)return;const o=n.fields.peek()[t];!k.peek()&&u&&w[e]&&!await E.confirm(`Remove the field "${Z(o.label||o.name)}"? The ${u} existing ${u===1?"entry keeps its":"entries keep their"} value for it, but it is no longer shown or edited. Nothing is lost until you Save - Discard puts it back.`)||j(n.fields.peek().filter(l=>l._key!==e))}function G(e,{isAdded:t=!1}={}){const o=Y(e);if(o<0)return;const l=n.fields.peek()[o],r=xt(l),c={};for(const[a,$]of Object.entries(r))c[a]=M.observable($);const f=JSON.stringify(r),V=()=>Object.fromEntries(Object.keys(c).map(a=>[a,c[a].peek()])),$e=()=>JSON.stringify(V())!==f;let Ce=!!l.name;const Ze=M.effect(()=>{const a=c.label.value;Ce||(c.name.value=gt(a))}),et=new Set(n.fields.peek().filter(a=>a._key!==e).map(a=>a.name)),Oe=M.computed(()=>{const a=Te(l,Object.fromEntries(Object.keys(c).map($=>[$,c[$].value])));return a.name&&et.has(a.name)?`"${a.name}" is used by another field.`:ht(a,w[e])}),tt=M.computed(()=>!k.peek()&&u&&w[e]&&c.name.value.trim()!==w[e]?`Entries keep their value under "${w[e]}" - renaming the field does not move it.`:""),nt={...c,problem:Oe,renamed:tt,isOptions:M.computed(()=>dt.has(c.type.value)),canAddon:M.computed(()=>it.has(c.type.value)),isGrid:M.computed(()=>n.layout.value==="grid")},b=E.slideover({title:t?"New field":`Field: ${Z(l.label||l.name||"Untitled")}`,size:"md",position:"right"}),T=Ot(`
9
9
  <div class="cex-panel">
10
10
  <div class="cex-grid">
11
11
  <div>
@@ -20,7 +20,7 @@ import{api as g}from"../api.js";import{addBannerAction as de}from"../lib/plugin-
20
20
  <div>
21
21
  <label class="form-label" for="cex-f-type">Type</label>
22
22
  <select id="cex-f-type" class="form-input" data-model="type.value">
23
- ${st.map(l=>`<option value="${l.value}">${l.label}</option>`).join("")}
23
+ ${rt.map(a=>`<option value="${a.value}">${a.label}</option>`).join("")}
24
24
  </select>
25
25
  </div>
26
26
  <div class="cex-setting">
@@ -42,6 +42,22 @@ import{api as g}from"../api.js";import{addBannerAction as de}from"../lib/plugin-
42
42
  <span data-help-title="Options" data-help="One per line, as value: Label - the value is what is stored, the label what is shown. A line with no colon uses the same text for both."></span></label>
43
43
  <textarea id="cex-f-options" class="form-input font-mono" rows="5" placeholder="s: Small&#10;m: Medium" data-model="options.value"></textarea>
44
44
  </div>
45
+ <div class="cex-wide cex-addons" data-bind-hidden="!canAddon.value">
46
+ <span class="form-label">Joined to the input
47
+ <span data-help-title="Icon or text beside the input" data-help="An icon or a short text joined to the start or end of the input in the entry form - a magnifier before a search box, \xA3 before a price, kg after a weight. Pick an icon, or type text; an icon wins if both are set."></span></span>
48
+ <div class="cex-addon-row">
49
+ <span class="cex-addon-side">Before</span>
50
+ <input id="cex-f-prefix-icon" type="text" class="form-input" maxlength="60" placeholder="Icon" aria-label="Icon before the input" data-model="prefixIcon.value">
51
+ <button type="button" class="btn btn-sm btn-ghost" title="Browse icons" aria-label="Browse icons for before the input" data-on-click="pickPrefix"><span data-icon="grid" data-icon-size="14"></span></button>
52
+ <input type="text" class="form-input" maxlength="24" placeholder="or text, e.g. \xA3" aria-label="Text before the input" data-model="prefixText.value">
53
+ </div>
54
+ <div class="cex-addon-row">
55
+ <span class="cex-addon-side">After</span>
56
+ <input id="cex-f-suffix-icon" type="text" class="form-input" maxlength="60" placeholder="Icon" aria-label="Icon after the input" data-model="suffixIcon.value">
57
+ <button type="button" class="btn btn-sm btn-ghost" title="Browse icons" aria-label="Browse icons for after the input" data-on-click="pickSuffix"><span data-icon="grid" data-icon-size="14"></span></button>
58
+ <input type="text" class="form-input" maxlength="24" placeholder="or text, e.g. kg" aria-label="Text after the input" data-model="suffixText.value">
59
+ </div>
60
+ </div>
45
61
  <div data-bind-hidden="!isGrid.value">
46
62
  <label class="form-label" for="cex-f-span">Column span
47
63
  <span data-help="How many of the grid's columns the field takes, in the entry form."></span></label>
@@ -61,4 +77,4 @@ import{api as g}from"../api.js";import{addBannerAction as de}from"../lib/plugin-
61
77
  <button type="button" class="btn btn-sm btn-ghost" data-on-click="cancel">Cancel</button>
62
78
  <button type="button" class="btn btn-sm btn-primary" data-on-click="done" data-bind-disabled="problem.value"><span data-icon="check" data-icon-size="13"></span> Done</button>
63
79
  </div>
64
- </div>`);let Q=!1,re=!1;const nt=b.close.bind(b);function je(){if(Oe.peek())return;const l=Ne(a,J());j(n.fields.peek().map($=>$._key===e?l:$)),Q=!0,b.close()}b.element.appendChild(F);const ot=Ae(F),at=M.applyBindings(tt,F,{methods:{done:je,cancel:()=>b.close(),remove:async()=>{Q=!0,b.close(),await oe(e)}}}),Re=F.querySelector("#cex-f-name");Re.addEventListener("input",()=>{Ce=!!Re.value}),F.addEventListener("keydown",l=>{(l.ctrlKey||l.metaKey)&&l.key==="Enter"&&(l.preventDefault(),je())}),lt(b,()=>{Xe(),at.dispose(),ot(),se.delete(b)}),b.close=async l=>{if(!Q&&l!==!0&&$e()){if(re)return;re=!0;const ce=await E.confirm("Discard the changes to this field?");if(re=!1,!ce)return}Q=!0;const $=nt();return t&&!$e()&&!a.name&&j(n.fields.peek().filter(ce=>ce._key!==e)),$},se.add(b),I.scan(F),b.open(),F.querySelector("#cex-f-label")?.focus()}function Ye(){const e=bt();j([...n.fields.peek(),e]),Ve.activate?.(1),G(e._key,{isAdded:!0})}function ze(e){const t=Y(e);if(t<0)return[];const o=n.fields.peek()[t],a=n.fields.peek().length;return[{header:o.label||o.name||"Field"},{label:"Edit\u2026",icon:"edit",action:()=>G(e)},{label:"Move up",icon:"arrow-up",disabled:t===0,action:()=>z(e,-1)},{label:"Move down",icon:"arrow-down",disabled:t===a-1,action:()=>z(e,1)},{label:"Duplicate",icon:"copy",action:()=>j(pt(n.fields.peek(),t))},{type:"divider"},{label:"Remove\u2026",icon:"trash",danger:!0,action:()=>oe(e)}]}async function ae(){if(U.peek())return;const e=m.peek();if(B(e)){E.toast(`Not saved - ${B(e)} thing${B(e)===1?"":"s"} to fix first (marked on the tabs).`,{type:"warning"});return}const t=yt(H(),{isNew:k.peek(),meta:Ie});U.value=!0;try{if(k.peek()){const a=await g.collections.create(t);N.value=_(H()),ie(),E.toast(`Created "${t.title}".`,{type:"success"}),R.navigate(`/collections/edit/${encodeURIComponent(a.slug)}`);return}const o=Te(K,t.storage.adapter);if(o&&u){if(await E.confirm(`You changed the storage (${o}). Move the ${u} existing ${u===1?"entry":"entries"} to the new storage?`)){const r=await g.collections.migrateStorage(c,t.storage);E.toast(`Moved ${r.migrated} of ${r.total} entries.`,{type:"success"})}else if(!await E.confirm(`Switch without moving them? The ${u} ${u===1?"entry stays":"entries stay"} in ${Z(K)} storage and this collection will show none until they are moved.`))return}if(await g.collections.update(c,t),!te())return;K=t.storage.adapter,o&&(u=(await g.collections.listEntries(c,{limit:1}).catch(()=>null))?.total??u);for(const a of n.fields.peek())x[a._key]=a.name;N.value=_(H()),ie(),Pe(d,t.title),E.toast("Collection saved.",{type:"success"})}catch(o){E.toast(`Not saved: ${o.message||o}`,{type:"error"})}finally{U.value=!1}}async function Ge(){if(!P.peek()||!await E.confirm("Discard the changes you have not saved?"))return;const e=De(O,{project:ve});for(const t of e.fields)x[t._key]=t.name;for(const[t,o]of Object.entries(e))n[t].value=o;N.value=_(e),W=!k.peek()}const q=i.querySelector(".cex-list");q.innerHTML=Mt;const Ve=E.tabs(i.querySelector(".cex-tabs"),{tabSelector:".tab-item",panelSelector:".tab-panel"}),Je=M.applyBindings(Ke,i,{methods:{save:()=>ae(),discard:()=>Ge(),addField:()=>Ye()}});h(()=>Je.dispose()),h(Ae(i,{position:"bottom"}));const le=i.querySelector("#field-slug"),xe=()=>{W=!!le.value};le.addEventListener("input",xe),h(()=>le.removeEventListener("input",xe));const se=new Set;h(()=>[...se].forEach(e=>{try{e.close(!0)}catch{}}));const Qe=E.contextMenu(q,{cursor:"glyph",match:".cex-row",inherit:!1,density:"compact",items:e=>ze(e.dataset.key)});h(()=>Qe?.destroy?.());const ke={click:e=>{const t=e.target.closest(".cex-row");if(!t)return;const o=e.target.closest(".cex-kebab");if(o){Fe(o);return}G(t.dataset.key)},keydown:e=>{const t=e.target.closest(".cex-row");!t||e.target!==t||(e.key==="F10"&&e.shiftKey?(e.preventDefault(),Fe(t)):e.key==="Enter"?G(t.dataset.key):e.altKey&&e.key==="ArrowUp"?(e.preventDefault(),z(t.dataset.key,-1)):e.altKey&&e.key==="ArrowDown"?(e.preventDefault(),z(t.dataset.key,1)):e.key==="Delete"&&(e.preventDefault(),oe(t.dataset.key)))}};for(const[e,t]of Object.entries(ke))q.addEventListener(e,t);if(h(()=>{for(const[e,t]of Object.entries(ke))q.removeEventListener(e,t)}),typeof E.sortable=="function"){const e=kt(),t=E.sortable(q,{items:".cex-row",key:"data-key",keyboard:!1,onStart:e.start,onEnd:({item:o})=>e.restore(o),onSort:({item:o,from:a,to:r})=>{e.restore(o),j(Le(n.fields.peek(),a,r))}});h(()=>t.destroy())}Pe(d,O?.title||"New collection"),de(s,{id:"collection-editor-back",icon:"arrow-left",title:"All collections",onClick:()=>R.navigate("/collections")}),c&&de(s,{id:"collection-editor-entries",icon:"list",title:"Open entries",onClick:()=>R.navigate(`/collections/${encodeURIComponent(c)}/entries`)}),de(s,{id:"collection-editor-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>ae()});const Ee=e=>{!(e.ctrlKey||e.metaKey)||e.key!=="s"||!te()||(e.preventDefault(),e.stopImmediatePropagation(),ae())};document.addEventListener("keydown",Ee,!0),h(()=>document.removeEventListener("keydown",Ee,!0));const V=St(c);function ie(){try{S.remove?S.remove(V):S.set(V,null)}catch{}}const Se=()=>{if(P.peek())try{S.set(V,{form:ut(H()),base:N.peek(),at:new Date().toISOString()})}catch{}},Me=e=>{P.peek()&&(Se(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",Me),h(()=>window.removeEventListener("beforeunload",Me)),y={path:C,dirty:()=>P.peek(),dispose(){pe=!0,Se(),ue.splice(0).reverse().forEach(e=>{try{e()}catch{}})}},I.scan(i);let A=null;try{A=S.get(V)}catch{}if(A?.form){ie();const e=A.base&&A.base!==N.peek(),t=A.at?` (${xt(A.at)})`:"",o=c?`changes to "${Z(O?.title||c)}"`:"a new collection",a=e?`You left unsaved ${o}${t}, but the saved collection has changed since. Restore your version anyway?`:`You left unsaved ${o}${t}. Restore them?`;if(await E.confirm(a)&&te()){const r=vt(A.form,f);D||(r.slug=f.slug);const v=Object.fromEntries(f.fields.filter(p=>p.id).map(p=>[p.id,p.name]));for(const p of r.fields)p.id&&v[p.id]!==void 0&&(x[p._key]=v[p.id]);we(r);for(const[p,J]of Object.entries(r))n[p]&&(n[p].value=J);D&&(W=!!r.slug&&r.slug!==X(r.title))}}}};
80
+ </div>`);let Q=!1,re=!1;const ot=b.close.bind(b);function je(){if(Oe.peek())return;const a=Te(l,V());j(n.fields.peek().map($=>$._key===e?a:$)),Q=!0,b.close()}b.element.appendChild(T);const at=Ie(T),lt=M.applyBindings(nt,T,{methods:{done:je,cancel:()=>b.close(),pickPrefix:()=>Pe(a=>{c.prefixIcon.value=a,c.prefixText.value=""}),pickSuffix:()=>Pe(a=>{c.suffixIcon.value=a,c.suffixText.value=""}),remove:async()=>{Q=!0,b.close(),await oe(e)}}}),Ae=T.querySelector("#cex-f-name");Ae.addEventListener("input",()=>{Ce=!!Ae.value}),T.addEventListener("keydown",a=>{(a.ctrlKey||a.metaKey)&&a.key==="Enter"&&(a.preventDefault(),je())}),st(b,()=>{Ze(),lt.dispose(),at(),se.delete(b)}),b.close=async a=>{if(!Q&&a!==!0&&$e()){if(re)return;re=!0;const ce=await E.confirm("Discard the changes to this field?");if(re=!1,!ce)return}Q=!0;const $=ot();return t&&!$e()&&!l.name&&j(n.fields.peek().filter(ce=>ce._key!==e)),$},se.add(b),I.scan(T),b.open(),T.querySelector("#cex-f-label")?.focus()}function ze(){const e=yt();j([...n.fields.peek(),e]),Ve.activate?.(1),G(e._key,{isAdded:!0})}function Ge(e){const t=Y(e);if(t<0)return[];const o=n.fields.peek()[t],l=n.fields.peek().length;return[{header:o.label||o.name||"Field"},{label:"Edit\u2026",icon:"edit",action:()=>G(e)},{label:"Move up",icon:"arrow-up",disabled:t===0,action:()=>z(e,-1)},{label:"Move down",icon:"arrow-down",disabled:t===l-1,action:()=>z(e,1)},{label:"Duplicate",icon:"copy",action:()=>j(mt(n.fields.peek(),t))},{type:"divider"},{label:"Remove\u2026",icon:"trash",danger:!0,action:()=>oe(e)}]}async function ae(){if(U.peek())return;const e=h.peek();if(q(e)){E.toast(`Not saved - ${q(e)} thing${q(e)===1?"":"s"} to fix first (marked on the tabs).`,{type:"warning"});return}const t=wt(H(),{isNew:k.peek(),meta:qe});U.value=!0;try{if(k.peek()){const l=await g.collections.create(t);F.value=_(H()),ie(),E.toast(`Created "${t.title}".`,{type:"success"}),R.navigate(`/collections/edit/${encodeURIComponent(l.slug)}`);return}const o=Ne(K,t.storage.adapter);if(o&&u){if(await E.confirm(`You changed the storage (${o}). Move the ${u} existing ${u===1?"entry":"entries"} to the new storage?`)){const r=await g.collections.migrateStorage(d,t.storage);E.toast(`Moved ${r.migrated} of ${r.total} entries.`,{type:"success"})}else if(!await E.confirm(`Switch without moving them? The ${u} ${u===1?"entry stays":"entries stay"} in ${Z(K)} storage and this collection will show none until they are moved.`))return}if(await g.collections.update(d,t),!te())return;K=t.storage.adapter,o&&(u=(await g.collections.listEntries(d,{limit:1}).catch(()=>null))?.total??u);for(const l of n.fields.peek())w[l._key]=l.name;F.value=_(H()),ie(),Be(p,t.title),E.toast("Collection saved.",{type:"success"})}catch(o){E.toast(`Not saved: ${o.message||o}`,{type:"error"})}finally{U.value=!1}}async function Je(){if(!B.peek()||!await E.confirm("Discard the changes you have not saved?"))return;const e=Fe(O,{project:he});for(const t of e.fields)w[t._key]=t.name;for(const[t,o]of Object.entries(e))n[t].value=o;F.value=_(e),W=!k.peek()}const L=i.querySelector(".cex-list");L.innerHTML=Ct;const Ve=E.tabs(i.querySelector(".cex-tabs"),{tabSelector:".tab-item",panelSelector:".tab-panel"}),Qe=M.applyBindings(Ue,i,{methods:{save:()=>ae(),discard:()=>Je(),addField:()=>ze()}});v(()=>Qe.dispose()),v(Ie(i,{position:"bottom"}));const le=i.querySelector("#field-slug"),we=()=>{W=!!le.value};le.addEventListener("input",we),v(()=>le.removeEventListener("input",we));const se=new Set;v(()=>[...se].forEach(e=>{try{e.close(!0)}catch{}}));const Xe=E.contextMenu(L,{cursor:"glyph",match:".cex-row",inherit:!1,density:"compact",items:e=>Ge(e.dataset.key)});v(()=>Xe?.destroy?.());const ke={click:e=>{const t=e.target.closest(".cex-row");if(!t)return;const o=e.target.closest(".cex-kebab");if(o){Re(o);return}G(t.dataset.key)},keydown:e=>{const t=e.target.closest(".cex-row");!t||e.target!==t||(e.key==="F10"&&e.shiftKey?(e.preventDefault(),Re(t)):e.key==="Enter"?G(t.dataset.key):e.altKey&&e.key==="ArrowUp"?(e.preventDefault(),z(t.dataset.key,-1)):e.altKey&&e.key==="ArrowDown"?(e.preventDefault(),z(t.dataset.key,1)):e.key==="Delete"&&(e.preventDefault(),oe(t.dataset.key)))}};for(const[e,t]of Object.entries(ke))L.addEventListener(e,t);if(v(()=>{for(const[e,t]of Object.entries(ke))L.removeEventListener(e,t)}),typeof E.sortable=="function"){const e=St(),t=E.sortable(L,{items:".cex-row",key:"data-key",keyboard:!1,onStart:e.start,onEnd:({item:o})=>e.restore(o),onSort:({item:o,from:l,to:r})=>{e.restore(o),j(De(n.fields.peek(),l,r))}});v(()=>t.destroy())}Be(p,O?.title||"New collection"),de(s,{id:"collection-editor-back",icon:"arrow-left",title:"All collections",onClick:()=>R.navigate("/collections")}),d&&de(s,{id:"collection-editor-entries",icon:"list",title:"Open entries",onClick:()=>R.navigate(`/collections/${encodeURIComponent(d)}/entries`)}),de(s,{id:"collection-editor-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>ae()});const Ee=e=>{!(e.ctrlKey||e.metaKey)||e.key!=="s"||!te()||(e.preventDefault(),e.stopImmediatePropagation(),ae())};document.addEventListener("keydown",Ee,!0),v(()=>document.removeEventListener("keydown",Ee,!0));const J=$t(d);function ie(){try{S.remove?S.remove(J):S.set(J,null)}catch{}}const Se=()=>{if(B.peek())try{S.set(J,{form:ft(H()),base:F.peek(),at:new Date().toISOString()})}catch{}},Me=e=>{B.peek()&&(Se(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",Me),v(()=>window.removeEventListener("beforeunload",Me)),y={path:C,dirty:()=>B.peek(),dispose(){ue=!0,Se(),pe.splice(0).reverse().forEach(e=>{try{e()}catch{}})}},I.scan(i);let A=null;try{A=S.get(J)}catch{}if(A?.form){ie();const e=A.base&&A.base!==F.peek(),t=A.at?` (${Et(A.at)})`:"",o=d?`changes to "${Z(O?.title||d)}"`:"a new collection",l=e?`You left unsaved ${o}${t}, but the saved collection has changed since. Restore your version anyway?`:`You left unsaved ${o}${t}. Restore them?`;if(await E.confirm(l)&&te()){const r=bt(A.form,m);D||(r.slug=m.slug);const c=Object.fromEntries(m.fields.filter(f=>f.id).map(f=>[f.id,f.name]));for(const f of r.fields)f.id&&c[f.id]!==void 0&&(w[f._key]=c[f.id]);xe(r);for(const[f,V]of Object.entries(r))n[f]&&(n[f].value=V);D&&(W=!!r.slug&&r.slug!==X(r.title))}}}};
@@ -4,7 +4,7 @@ import{api as g,apiRequest as it}from"../api.js";import{addBannerAction as z}fro
4
4
  ${Array.from({length:m},(v,a)=>`<td data-col="${a}" title="{{t${a}}}">{{c${a}}}</td>`).join("")}
5
5
  <td class="cex-when" title="{{whenFull}}">{{when}}</td>
6
6
  <td class="cex-tools"><button type="button" class="cex-run" data-bind-hidden="!runs" title="Run one of this collection's actions">\u26A1 {{runs}}</button><button type="button" class="cex-kebab" aria-label="Actions for this entry" title="Actions">\u22EE</button></td>
7
- </tr>`}function wt(m,h){const v={};return m.forEach(a=>{const u={};a.fullWidth?u.span=h:a.span>1&&(u.span=a.span),a.placeholder&&(u.placeholder=a.placeholder),a.helper&&(u.hint=a.helper);const r=a.type==="checkbox"?"boolean":a.type==="date"?"string":a.type;v[a.name]={type:r,label:a.label,required:a.required,options:a.options,...Object.keys(u).length?{formConfig:u}:{}}}),v}function ce(m,h){const v={};for(const a of h){const u=[...m.querySelectorAll(`[name="${CSS.escape(a.name)}"]`)];if(!u.length)continue;const r=u[0];r.type==="radio"?v[a.name]=u.find(p=>p.checked)?.value??"":r.type==="checkbox"&&u.length>1?v[a.name]=u.filter(p=>p.checked).map(p=>p.value):r.type==="checkbox"?v[a.name]=r.checked:r.tagName==="SELECT"&&r.multiple?v[a.name]=[...r.selectedOptions].map(p=>p.value):v[a.name]=gt(a,r.value)}return v}function Fe(m,h,v){for(const a of h){if(!(a.name in v))continue;const u=v[a.name],r=[...m.querySelectorAll(`[name="${CSS.escape(a.name)}"]`)];if(!r.length)continue;const p=r[0];p.type==="radio"?r.forEach(c=>{c.checked=c.value===String(u)}):p.type==="checkbox"&&r.length>1?r.forEach(c=>{c.checked=Array.isArray(u)&&u.includes(c.value)}):p.type==="checkbox"?p.checked=u===!0:p.tagName==="SELECT"&&p.multiple?[...p.options].forEach(c=>{c.selected=Array.isArray(u)&&u.includes(c.value)}):p.value=u??"",r.forEach(c=>c.dispatchEvent(new Event("input",{bubbles:!0})))}}const Pe=dt(Be);export const collectionEntriesView={templateUrl:"/admin/js/templates/collection-entries.html",async onMount(m){Pe.reset();const h=m.get(0).querySelector(".cex")||m.get(0),v=location.hash.match(/^#\/collections\/([^/?#]+)\/entries/),a=v?decodeURIComponent(v[1]):null,u=()=>Be.test(location.hash)&&h.isConnected&&location.hash.includes(`/collections/${encodeURIComponent(a)}/entries`),r=e=>(Pe.add(e),e);if(!a){E.toast("No collection selected.",{type:"error"});return}const p=M.observable(null),c=M.observable([]),x=M.observable(0),Y=M.observable(!0),_=M.observable(""),_e=M.computed(()=>p.value!==null),le=M.observable(""),w=M.observable([]),Z=M.observable({key:"created",dir:"desc"}),y=M.observable([]),q=M.observable(X),J=M.observable([]),Q=M.observable({}),A=M.observable(!1);let ee=1;const ie=()=>p.peek()?.fields||[],re=M.computed(()=>(p.value?.fields||[]).slice(0,ut)),V=e=>c.peek().find(t=>t.id===e),ye=e=>J.peek().filter(t=>{const n=Q.peek()[t.slug];return n==null||n.has(e)}),T=M.observable([]),ve=M.observable([]);r(M.effect(()=>{const e=pt(c.value,{filters:w.value,query:le.value,sort:Z.value});T.value=e})),r(M.effect(()=>{const e=re.value,t=new Set(y.value);Q.value,J.value,ve.value=T.value.slice(0,q.value).map(n=>{const s={id:n.id,sel:t.has(n.id),rowCls:t.has(n.id)?"is-selected":""};e.forEach((i,d)=>{const f=he(i,n.data?.[i.name]);s[`c${d}`]=Ue(f),s[`t${d}`]=je(f)});const o=n.meta?.createdAt;s.when=o?Et(o):"",s.whenFull=o?`Created ${B(o)}${n.meta?.updatedAt?` \xB7 changed ${B(n.meta.updatedAt)}`:""}`:"";const l=ye(n.id).length;return s.runs=l?String(l):"",s})}));const Je=M.computed(()=>w.value.map((e,t)=>({key:`${t}:${e.field}:${e.op}:${e.value}`,idx:t,text:vt(e)}))),Ve=M.computed(()=>y.value.length),He={schema:p,loaded:c,loading:Y,loadError:_,ready:_e,query:le,rows:ve,chips:Je,busy:A,selectedCount:Ve,countLine:M.computed(()=>mt(T.value.length,c.value.length,x.value)),selectedText:M.computed(()=>`${y.value.length} selected`),canClear:M.computed(()=>ft(a)),introText:M.computed(()=>{const e=p.value;return e?`${e.description?`${e.description} - `:""}${(e.fields||[]).length} field${(e.fields||[]).length===1?"":"s"}${e.storage?.adapter==="mongodb"?", on MongoDB":""}.`:""}),emptyText:M.computed(()=>c.value.length?"No entries match the search or filters.":"No entries yet - Add entry in the banner makes one, or Import brings many."),moreText:M.computed(()=>{const e=T.value.length-q.value;if(e>0)return`Show ${Math.min(X,e)} more (${e} not shown)`;const t=x.value-c.value.length;return t>0?`Load the next ${Math.min(se,t)} from the server`:""})};async function H({reset:e=!0}={}){Y.value=!0;try{const t=e?Math.max(se,c.peek().length):se;e&&(ee=1);const n=await g.collections.listEntries(a,{limit:t,page:ee});if(!u())return;const s=n?.entries||(Array.isArray(n)?n:[]);c.value=e?s:[...c.peek(),...s.filter(l=>!V(l.id))],x.value=Number(n?.total)||c.peek().length,e&&(ee=Math.max(1,Math.ceil(c.peek().length/se)));const o=new Set(c.peek().map(l=>l.id));y.value=y.peek().filter(l=>o.has(l)),_.value="",await We()}catch(t){_.value=`Could not load the entries: ${t.message||t}`}finally{Y.value=!1}}async function Ke(){ee+=1,await H({reset:!1})}async function Ge(){try{if((await it("/actions/_count",{method:"GET"}).catch(()=>null))?.available===!1)return;const t=await g.actions.forCollection(a);J.value=Array.isArray(t)?t:[]}catch{J.value=[]}}async function We(){const e=J.peek().filter(s=>s.access?.rowLevel);if(!e.length||!c.peek().length)return;const t=c.peek().map(s=>s.id),n={...Q.peek()};await Promise.all(e.map(async s=>{try{n[s.slug]=new Set((await g.actions.checkAccess(s.slug,t)).allowed)}catch{n[s.slug]=null}})),Q.value=n}const K=e=>{const t=ie()[0];return(t?Ue(he(t,e?.data?.[t.name]),40):"")||`Entry ${String(e?.id||"").slice(0,8)}`};async function be(e){if(await E.confirm(`Delete \u201C${D(K(e))}\u201D? This cannot be undone.`))try{await g.collections.deleteEntry(a,e.id),c.value=c.peek().filter(t=>t.id!==e.id),x.value=Math.max(0,x.peek()-1),y.value=y.peek().filter(t=>t!==e.id),j("#/collections"),E.toast("Entry deleted.",{type:"success"})}catch(t){E.toast(`Not deleted: ${t.message||t}`,{type:"error"})}}async function Xe(){const e=[...y.peek()];if(!e.length||!await E.confirm(`Delete ${e.length} entr${e.length===1?"y":"ies"}? This cannot be undone.`))return;A.value=!0;let t=0;const n=[];for(const s of e)try{await g.collections.deleteEntry(a,s),t++}catch(o){n.push(o.message||String(o))}A.value=!1,y.value=[],j("#/collections"),E.toast(n.length?`Deleted ${t}; ${n.length} refused (${n[0]}).`:`Deleted ${t} entr${t===1?"y":"ies"}.`,{type:n.length?"warning":"success",duration:n.length?7e3:4e3}),u()&&await H()}async function Ye(){const e=p.peek();if(await E.confirm(`Delete ALL ${x.peek()} entries in \u201C${D(e?.title||a)}\u201D? This cannot be undone.`)){A.value=!0;try{await g.collections.clearEntries(a),c.value=[],x.value=0,y.value=[],j("#/collections"),E.toast("All entries deleted.",{type:"success"})}catch(t){E.toast(`Not cleared: ${t.message||t}`,{type:"error"})}finally{A.value=!1}}}async function Ze(e){try{const t=await g.collections.createEntry(a,{...e.data||{}});c.value=[t,...c.peek()],x.value=x.peek()+1,j("#/collections"),E.toast("Duplicated - the copy is at the top.",{type:"success"})}catch(t){E.toast(`Not duplicated: ${t.message||t}`,{type:"error"})}}async function Qe(e,t){const n=e.trigger?.confirmMessage;if(!(n&&!await E.confirm(D(n)))){A.value=!0;try{const s=await g.actions.execute(e.slug,t.id);if(s?.success){const o=s.stepsCompleted??0;E.toast(`${e.trigger?.label||e.title||"Action"} done (${o} step${o===1?"":"s"}).`,{type:"success"}),u()&&await H()}else E.toast(s?.results?.find(o=>!o.success)?.error||"The action failed.",{type:"error"})}catch(s){E.toast(s.message||"The action failed.",{type:"error"})}finally{A.value=!1}}}async function ge(e){try{await navigator.clipboard.writeText(e),E.toast("Copied.",{type:"success",duration:1500})}catch{E.toast("Copy failed - your browser refused the clipboard.",{type:"error"})}}function de(e){const t=y.peek();y.value=t.includes(e)?t.filter(n=>n!==e):[...t,e]}function Ee(e,t,n){w.value=[...w.peek(),{field:e.name,label:e.label||e.name,op:t,value:n}],q.value=X}const ue=new Set;let N=null,ke=!1;function xe(e,t,n){const s=e.close.bind(e);let o=!1,l=!1;e.close=async i=>{if(!o&&i!==!0&&!ke&&t()){if(l)return;l=!0;const d=await E.confirm(n);if(l=!1,!d)return}return o=!0,s()}}function te(e,t=()=>{}){ue.add(e),rt(e,()=>{ue.delete(e),t()})}function O(e,t=null){const n=p.peek(),s=ie(),o=!!e,l=E.slideover({title:o?`Edit ${D(K(e))}`:"New entry",size:"md",position:"right"}),i=oe(`
7
+ </tr>`}function wt(m,h){const v={};return m.forEach(a=>{const r={};a.fullWidth?r.span=h:a.span>1&&(r.span=a.span),a.placeholder&&(r.placeholder=a.placeholder),a.helper&&(r.hint=a.helper),a.prefix&&(r.prefix=a.prefix),a.suffix&&(r.suffix=a.suffix);const d=a.type==="checkbox"?"boolean":a.type==="date"?"string":a.type;v[a.name]={type:d,label:a.label,required:a.required,options:a.options,...Object.keys(r).length?{formConfig:r}:{}}}),v}function ce(m,h){const v={};for(const a of h){const r=[...m.querySelectorAll(`[name="${CSS.escape(a.name)}"]`)];if(!r.length)continue;const d=r[0];d.type==="radio"?v[a.name]=r.find(p=>p.checked)?.value??"":d.type==="checkbox"&&r.length>1?v[a.name]=r.filter(p=>p.checked).map(p=>p.value):d.type==="checkbox"?v[a.name]=d.checked:d.tagName==="SELECT"&&d.multiple?v[a.name]=[...d.selectedOptions].map(p=>p.value):v[a.name]=gt(a,d.value)}return v}function Fe(m,h,v){for(const a of h){if(!(a.name in v))continue;const r=v[a.name],d=[...m.querySelectorAll(`[name="${CSS.escape(a.name)}"]`)];if(!d.length)continue;const p=d[0];p.type==="radio"?d.forEach(c=>{c.checked=c.value===String(r)}):p.type==="checkbox"&&d.length>1?d.forEach(c=>{c.checked=Array.isArray(r)&&r.includes(c.value)}):p.type==="checkbox"?p.checked=r===!0:p.tagName==="SELECT"&&p.multiple?[...p.options].forEach(c=>{c.selected=Array.isArray(r)&&r.includes(c.value)}):p.value=r??"",d.forEach(c=>c.dispatchEvent(new Event("input",{bubbles:!0})))}}const Pe=dt(Be);export const collectionEntriesView={templateUrl:"/admin/js/templates/collection-entries.html",async onMount(m){Pe.reset();const h=m.get(0).querySelector(".cex")||m.get(0),v=location.hash.match(/^#\/collections\/([^/?#]+)\/entries/),a=v?decodeURIComponent(v[1]):null,r=()=>Be.test(location.hash)&&h.isConnected&&location.hash.includes(`/collections/${encodeURIComponent(a)}/entries`),d=e=>(Pe.add(e),e);if(!a){E.toast("No collection selected.",{type:"error"});return}const p=M.observable(null),c=M.observable([]),x=M.observable(0),Y=M.observable(!0),_=M.observable(""),_e=M.computed(()=>p.value!==null),le=M.observable(""),w=M.observable([]),Z=M.observable({key:"created",dir:"desc"}),y=M.observable([]),q=M.observable(X),J=M.observable([]),Q=M.observable({}),A=M.observable(!1);let ee=1;const ie=()=>p.peek()?.fields||[],re=M.computed(()=>(p.value?.fields||[]).slice(0,ut)),V=e=>c.peek().find(t=>t.id===e),ye=e=>J.peek().filter(t=>{const n=Q.peek()[t.slug];return n==null||n.has(e)}),T=M.observable([]),ve=M.observable([]);d(M.effect(()=>{const e=pt(c.value,{filters:w.value,query:le.value,sort:Z.value});T.value=e})),d(M.effect(()=>{const e=re.value,t=new Set(y.value);Q.value,J.value,ve.value=T.value.slice(0,q.value).map(n=>{const s={id:n.id,sel:t.has(n.id),rowCls:t.has(n.id)?"is-selected":""};e.forEach((i,u)=>{const f=he(i,n.data?.[i.name]);s[`c${u}`]=Ue(f),s[`t${u}`]=je(f)});const o=n.meta?.createdAt;s.when=o?Et(o):"",s.whenFull=o?`Created ${B(o)}${n.meta?.updatedAt?` \xB7 changed ${B(n.meta.updatedAt)}`:""}`:"";const l=ye(n.id).length;return s.runs=l?String(l):"",s})}));const Je=M.computed(()=>w.value.map((e,t)=>({key:`${t}:${e.field}:${e.op}:${e.value}`,idx:t,text:vt(e)}))),Ve=M.computed(()=>y.value.length),He={schema:p,loaded:c,loading:Y,loadError:_,ready:_e,query:le,rows:ve,chips:Je,busy:A,selectedCount:Ve,countLine:M.computed(()=>mt(T.value.length,c.value.length,x.value)),selectedText:M.computed(()=>`${y.value.length} selected`),canClear:M.computed(()=>ft(a)),introText:M.computed(()=>{const e=p.value;return e?`${e.description?`${e.description} - `:""}${(e.fields||[]).length} field${(e.fields||[]).length===1?"":"s"}${e.storage?.adapter==="mongodb"?", on MongoDB":""}.`:""}),emptyText:M.computed(()=>c.value.length?"No entries match the search or filters.":"No entries yet - Add entry in the banner makes one, or Import brings many."),moreText:M.computed(()=>{const e=T.value.length-q.value;if(e>0)return`Show ${Math.min(X,e)} more (${e} not shown)`;const t=x.value-c.value.length;return t>0?`Load the next ${Math.min(se,t)} from the server`:""})};async function H({reset:e=!0}={}){Y.value=!0;try{const t=e?Math.max(se,c.peek().length):se;e&&(ee=1);const n=await g.collections.listEntries(a,{limit:t,page:ee});if(!r())return;const s=n?.entries||(Array.isArray(n)?n:[]);c.value=e?s:[...c.peek(),...s.filter(l=>!V(l.id))],x.value=Number(n?.total)||c.peek().length,e&&(ee=Math.max(1,Math.ceil(c.peek().length/se)));const o=new Set(c.peek().map(l=>l.id));y.value=y.peek().filter(l=>o.has(l)),_.value="",await We()}catch(t){_.value=`Could not load the entries: ${t.message||t}`}finally{Y.value=!1}}async function Ke(){ee+=1,await H({reset:!1})}async function Ge(){try{if((await it("/actions/_count",{method:"GET"}).catch(()=>null))?.available===!1)return;const t=await g.actions.forCollection(a);J.value=Array.isArray(t)?t:[]}catch{J.value=[]}}async function We(){const e=J.peek().filter(s=>s.access?.rowLevel);if(!e.length||!c.peek().length)return;const t=c.peek().map(s=>s.id),n={...Q.peek()};await Promise.all(e.map(async s=>{try{n[s.slug]=new Set((await g.actions.checkAccess(s.slug,t)).allowed)}catch{n[s.slug]=null}})),Q.value=n}const K=e=>{const t=ie()[0];return(t?Ue(he(t,e?.data?.[t.name]),40):"")||`Entry ${String(e?.id||"").slice(0,8)}`};async function be(e){if(await E.confirm(`Delete \u201C${D(K(e))}\u201D? This cannot be undone.`))try{await g.collections.deleteEntry(a,e.id),c.value=c.peek().filter(t=>t.id!==e.id),x.value=Math.max(0,x.peek()-1),y.value=y.peek().filter(t=>t!==e.id),j("#/collections"),E.toast("Entry deleted.",{type:"success"})}catch(t){E.toast(`Not deleted: ${t.message||t}`,{type:"error"})}}async function Xe(){const e=[...y.peek()];if(!e.length||!await E.confirm(`Delete ${e.length} entr${e.length===1?"y":"ies"}? This cannot be undone.`))return;A.value=!0;let t=0;const n=[];for(const s of e)try{await g.collections.deleteEntry(a,s),t++}catch(o){n.push(o.message||String(o))}A.value=!1,y.value=[],j("#/collections"),E.toast(n.length?`Deleted ${t}; ${n.length} refused (${n[0]}).`:`Deleted ${t} entr${t===1?"y":"ies"}.`,{type:n.length?"warning":"success",duration:n.length?7e3:4e3}),r()&&await H()}async function Ye(){const e=p.peek();if(await E.confirm(`Delete ALL ${x.peek()} entries in \u201C${D(e?.title||a)}\u201D? This cannot be undone.`)){A.value=!0;try{await g.collections.clearEntries(a),c.value=[],x.value=0,y.value=[],j("#/collections"),E.toast("All entries deleted.",{type:"success"})}catch(t){E.toast(`Not cleared: ${t.message||t}`,{type:"error"})}finally{A.value=!1}}}async function Ze(e){try{const t=await g.collections.createEntry(a,{...e.data||{}});c.value=[t,...c.peek()],x.value=x.peek()+1,j("#/collections"),E.toast("Duplicated - the copy is at the top.",{type:"success"})}catch(t){E.toast(`Not duplicated: ${t.message||t}`,{type:"error"})}}async function Qe(e,t){const n=e.trigger?.confirmMessage;if(!(n&&!await E.confirm(D(n)))){A.value=!0;try{const s=await g.actions.execute(e.slug,t.id);if(s?.success){const o=s.stepsCompleted??0;E.toast(`${e.trigger?.label||e.title||"Action"} done (${o} step${o===1?"":"s"}).`,{type:"success"}),r()&&await H()}else E.toast(s?.results?.find(o=>!o.success)?.error||"The action failed.",{type:"error"})}catch(s){E.toast(s.message||"The action failed.",{type:"error"})}finally{A.value=!1}}}async function ge(e){try{await navigator.clipboard.writeText(e),E.toast("Copied.",{type:"success",duration:1500})}catch{E.toast("Copy failed - your browser refused the clipboard.",{type:"error"})}}function de(e){const t=y.peek();y.value=t.includes(e)?t.filter(n=>n!==e):[...t,e]}function Ee(e,t,n){w.value=[...w.peek(),{field:e.name,label:e.label||e.name,op:t,value:n}],q.value=X}const ue=new Set;let N=null,ke=!1;function xe(e,t,n){const s=e.close.bind(e);let o=!1,l=!1;e.close=async i=>{if(!o&&i!==!0&&!ke&&t()){if(l)return;l=!0;const u=await E.confirm(n);if(l=!1,!u)return}return o=!0,s()}}function te(e,t=()=>{}){ue.add(e),rt(e,()=>{ue.delete(e),t()})}function O(e,t=null){const n=p.peek(),s=ie(),o=!!e,l=E.slideover({title:o?`Edit ${D(K(e))}`:"New entry",size:"md",position:"right"}),i=oe(`
8
8
  <div class="cex-form">
9
9
  <div class="cex-fields"></div>
10
10
  <small class="cex-hint cex-when-line"></small>
@@ -15,7 +15,7 @@ import{api as g,apiRequest as it}from"../api.js";import{addBannerAction as z}fro
15
15
  <button type="button" class="btn btn-sm btn-ghost" data-act="cancel">Cancel</button>
16
16
  <button type="button" class="btn btn-sm btn-primary" data-act="save"><span data-icon="check" data-icon-size="13"></span> <span class="cex-save-text"></span></button>
17
17
  </div>
18
- </div>`);i.querySelector(".cex-save-text").textContent=o?"Save":"Add entry";const d=i.querySelector(".cex-fields");F.create(wt(s,n?.columns||2),e?.data||{},{layout:n?.layout||"stacked",columns:n?.columns||2,showSubmitButton:!1}).renderTo(d),s.forEach(k=>{if(k.type!=="date"||!k.name)return;const U=d.querySelector(`[name="${CSS.escape(k.name)}"]`);U&&U.type!=="date"&&(U.type="date")}),e?.data&&Fe(d,s,e.data),o&&e.meta?.createdAt&&(i.querySelector(".cex-when-line").textContent=`Created ${B(e.meta.createdAt)}${e.meta.updatedAt?`, last changed ${B(e.meta.updatedAt)}`:""}${e.meta.source?` \xB7 via ${e.meta.source}`:""}.`),l.element.appendChild(i);const f=ze(ce(d,s));t&&Fe(d,s,t);const b=()=>ze(ce(d,s))!==f,lt=i.querySelector(".cex-dirty"),fe=()=>{lt.hidden=!b()};d.addEventListener("input",fe),d.addEventListener("change",fe),fe();let me=!1;async function Ne(){if(me)return;document.activeElement?.dispatchEvent?.(new Event("change",{bubbles:!0}));const k=ce(d,s);me=!0;const U=i.querySelector('[data-act="save"]');U.disabled=!0;try{if(o){const W=await g.collections.updateEntry(a,e.id,k);c.value=c.peek().map(De=>De.id===e.id?W:De),E.toast("Entry saved.",{type:"success"})}else{const W=await g.collections.createEntry(a,k);c.value=[W,...c.peek()],x.value=x.peek()+1,j("#/collections"),E.toast("Entry added.",{type:"success"})}try{S.remove?S.remove(P(a)):S.set(P(a),null)}catch{}l.close(!0)}catch(W){E.toast(W.message||"Not saved.",{type:"error"})}finally{me=!1,U.disabled=!1}}i.querySelector('[data-act="save"]').addEventListener("click",Ne),i.querySelector('[data-act="cancel"]').addEventListener("click",()=>l.close()),i.addEventListener("keydown",k=>{(k.ctrlKey||k.metaKey)&&(k.key==="Enter"||k.key==="s")&&(k.preventDefault(),Ne())});const Ie={id:e?.id??null,dirty:b,values:()=>ht(ce(d,s),s)};N=Ie,te(l,()=>{N===Ie&&(N=null)}),xe(l,b,"Discard the changes you have not saved?"),I.scan(i),l.open(),d.querySelector("input, select, textarea")?.focus()}function et(e){const t=E.slideover({title:D(K(e)),size:"md",position:"right"}),n=oe(`
18
+ </div>`);i.querySelector(".cex-save-text").textContent=o?"Save":"Add entry";const u=i.querySelector(".cex-fields");F.create(wt(s,n?.columns||2),e?.data||{},{layout:n?.layout||"stacked",columns:n?.columns||2,showSubmitButton:!1}).renderTo(u),s.forEach(k=>{if(k.type!=="date"||!k.name)return;const U=u.querySelector(`[name="${CSS.escape(k.name)}"]`);U&&U.type!=="date"&&(U.type="date")}),e?.data&&Fe(u,s,e.data),o&&e.meta?.createdAt&&(i.querySelector(".cex-when-line").textContent=`Created ${B(e.meta.createdAt)}${e.meta.updatedAt?`, last changed ${B(e.meta.updatedAt)}`:""}${e.meta.source?` \xB7 via ${e.meta.source}`:""}.`),l.element.appendChild(i);const f=ze(ce(u,s));t&&Fe(u,s,t);const b=()=>ze(ce(u,s))!==f,lt=i.querySelector(".cex-dirty"),fe=()=>{lt.hidden=!b()};u.addEventListener("input",fe),u.addEventListener("change",fe),fe();let me=!1;async function Ne(){if(me)return;document.activeElement?.dispatchEvent?.(new Event("change",{bubbles:!0}));const k=ce(u,s);me=!0;const U=i.querySelector('[data-act="save"]');U.disabled=!0;try{if(o){const W=await g.collections.updateEntry(a,e.id,k);c.value=c.peek().map(De=>De.id===e.id?W:De),E.toast("Entry saved.",{type:"success"})}else{const W=await g.collections.createEntry(a,k);c.value=[W,...c.peek()],x.value=x.peek()+1,j("#/collections"),E.toast("Entry added.",{type:"success"})}try{S.remove?S.remove(P(a)):S.set(P(a),null)}catch{}l.close(!0)}catch(W){E.toast(W.message||"Not saved.",{type:"error"})}finally{me=!1,U.disabled=!1}}i.querySelector('[data-act="save"]').addEventListener("click",Ne),i.querySelector('[data-act="cancel"]').addEventListener("click",()=>l.close()),i.addEventListener("keydown",k=>{(k.ctrlKey||k.metaKey)&&(k.key==="Enter"||k.key==="s")&&(k.preventDefault(),Ne())});const Ie={id:e?.id??null,dirty:b,values:()=>ht(ce(u,s),s)};N=Ie,te(l,()=>{N===Ie&&(N=null)}),xe(l,b,"Discard the changes you have not saved?"),I.scan(i),l.open(),u.querySelector("input, select, textarea")?.focus()}function et(e){const t=E.slideover({title:D(K(e)),size:"md",position:"right"}),n=oe(`
19
19
  <div class="cex-form">
20
20
  <dl class="cex-detail"></dl>
21
21
  <div class="cex-meta"></div>
@@ -24,7 +24,7 @@ import{api as g,apiRequest as it}from"../api.js";import{addBannerAction as z}fro
24
24
  <span class="cex-spacer"></span>
25
25
  <button type="button" class="btn btn-sm btn-primary" data-act="edit"><span data-icon="edit" data-icon-size="13"></span> Edit</button>
26
26
  </div>
27
- </div>`),s=n.querySelector(".cex-detail");for(const i of ie()){const d=je(he(i,e.data?.[i.name])),f=document.createElement("dt");f.textContent=i.label||i.name;const b=document.createElement("dd");b.textContent=d===""?"-":d,s.append(f,b)}const o=n.querySelector(".cex-meta"),l=i=>{const d=document.createElement("span");d.textContent=i,o.appendChild(d)};l(`Id: ${e.id}`),e.meta?.createdAt&&l(`Created: ${B(e.meta.createdAt)}`),e.meta?.updatedAt&&l(`Changed: ${B(e.meta.updatedAt)}`),e.meta?.source&&l(`Source: ${e.meta.source}`),n.querySelector('[data-act="copy"]').addEventListener("click",()=>ge(JSON.stringify(e,null,2))),n.querySelector('[data-act="edit"]').addEventListener("click",()=>{t.close(!0),O(e)}),t.element.appendChild(n),te(t),I.scan(n),t.open()}function tt(){const e=E.slideover({title:"Import entries",size:"md",position:"right"}),t=oe(`
27
+ </div>`),s=n.querySelector(".cex-detail");for(const i of ie()){const u=je(he(i,e.data?.[i.name])),f=document.createElement("dt");f.textContent=i.label||i.name;const b=document.createElement("dd");b.textContent=u===""?"-":u,s.append(f,b)}const o=n.querySelector(".cex-meta"),l=i=>{const u=document.createElement("span");u.textContent=i,o.appendChild(u)};l(`Id: ${e.id}`),e.meta?.createdAt&&l(`Created: ${B(e.meta.createdAt)}`),e.meta?.updatedAt&&l(`Changed: ${B(e.meta.updatedAt)}`),e.meta?.source&&l(`Source: ${e.meta.source}`),n.querySelector('[data-act="copy"]').addEventListener("click",()=>ge(JSON.stringify(e,null,2))),n.querySelector('[data-act="edit"]').addEventListener("click",()=>{t.close(!0),O(e)}),t.element.appendChild(n),te(t),I.scan(n),t.open()}function tt(){const e=E.slideover({title:"Import entries",size:"md",position:"right"}),t=oe(`
28
28
  <div class="cex-form">
29
29
  <p class="cex-hint">A JSON array of entries, each with a "data" object of field values - what Export gives you.
30
30
  <span data-help="Each entry is added as new, with a new id and today's date (the Data Transfer plugin from the Marketplace keeps ids and dates). Entries are checked like any other save - one that fails, or points a reference at an entry that does not exist, is skipped and reported. Values are stored as given, including fields the collection does not have."></span></p>
@@ -40,11 +40,11 @@ import{api as g,apiRequest as it}from"../api.js";import{addBannerAction as z}fro
40
40
  <button type="button" class="btn btn-sm btn-ghost" data-act="cancel">Cancel</button>
41
41
  <button type="button" class="btn btn-sm btn-primary" data-act="import"><span data-icon="upload" data-icon-size="13"></span> Import</button>
42
42
  </div>
43
- </div>`),n=t.querySelector("textarea"),s=t.querySelector(".cex-err"),o=t.querySelector(".cex-ok"),l=t.querySelector('input[type="file"]');t.querySelector('[data-act="file"]').addEventListener("click",()=>l.click()),l.addEventListener("change",async()=>{const d=l.files?.[0];l.value="",d&&(n.value=await d.text())}),t.querySelector('[data-act="cancel"]').addEventListener("click",()=>e.close());let i=!1;t.querySelector('[data-act="import"]').addEventListener("click",async()=>{s.hidden=!0,o.hidden=!0;let d;try{if(d=JSON.parse(n.value),!Array.isArray(d))throw new Error("it must be a JSON array")}catch(b){s.textContent=`Not valid: ${b.message}`,s.hidden=!1;return}const f=t.querySelector('[data-act="import"]');f.disabled=!0;try{const b=await g.collections.import(a,d);o.textContent=`Imported ${b.imported}; skipped ${b.skipped}.`,o.hidden=!1,i=!0,j("#/collections"),u()&&await H(),setTimeout(()=>e.close(!0),1200)}catch(b){s.textContent=b.message||"Import failed.",s.hidden=!1}finally{f.disabled=!1}}),e.element.appendChild(t),te(e),xe(e,()=>!i&&n.value.trim()!=="","Discard the entries you pasted?"),Oe(t),I.scan(t),e.open(),n.focus()}function nt(){const e=E.slideover({title:"Export entries",size:"sm",position:"right"}),t=oe(`
43
+ </div>`),n=t.querySelector("textarea"),s=t.querySelector(".cex-err"),o=t.querySelector(".cex-ok"),l=t.querySelector('input[type="file"]');t.querySelector('[data-act="file"]').addEventListener("click",()=>l.click()),l.addEventListener("change",async()=>{const u=l.files?.[0];l.value="",u&&(n.value=await u.text())}),t.querySelector('[data-act="cancel"]').addEventListener("click",()=>e.close());let i=!1;t.querySelector('[data-act="import"]').addEventListener("click",async()=>{s.hidden=!0,o.hidden=!0;let u;try{if(u=JSON.parse(n.value),!Array.isArray(u))throw new Error("it must be a JSON array")}catch(b){s.textContent=`Not valid: ${b.message}`,s.hidden=!1;return}const f=t.querySelector('[data-act="import"]');f.disabled=!0;try{const b=await g.collections.import(a,u);o.textContent=`Imported ${b.imported}; skipped ${b.skipped}.`,o.hidden=!1,i=!0,j("#/collections"),r()&&await H(),setTimeout(()=>e.close(!0),1200)}catch(b){s.textContent=b.message||"Import failed.",s.hidden=!1}finally{f.disabled=!1}}),e.element.appendChild(t),te(e),xe(e,()=>!i&&n.value.trim()!=="","Discard the entries you pasted?"),Oe(t),I.scan(t),e.open(),n.focus()}function nt(){const e=E.slideover({title:"Export entries",size:"sm",position:"right"}),t=oe(`
44
44
  <div class="cex-form">
45
45
  <p class="cex-hint">Every entry in the collection - not only the ones shown.</p>
46
46
  <div class="cex-export">
47
47
  <button type="button" class="btn btn-ghost" data-fmt="json"><span data-icon="download" data-icon-size="14"></span> Export as JSON</button>
48
48
  <button type="button" class="btn btn-ghost" data-fmt="csv"><span data-icon="download" data-icon-size="14"></span> Export as CSV</button>
49
49
  </div>
50
- </div>`);t.querySelectorAll("[data-fmt]").forEach(n=>n.addEventListener("click",async()=>{const s=n.dataset.fmt;try{const o=await fetch(`/api/collections/${encodeURIComponent(a)}/export?format=${s}`,{headers:{Authorization:`Bearer ${S.get("auth_token")||""}`}});if(!o.ok)throw new Error(`${o.status}`);const l=await o.blob(),i=document.createElement("a");i.href=URL.createObjectURL(l),i.download=`${a}-entries.${s}`,document.body.appendChild(i),i.click(),i.remove(),setTimeout(()=>URL.revokeObjectURL(i.href),1e3),e.close(!0)}catch(o){E.toast(`Export failed (${o.message}).`,{type:"error"})}})),e.element.appendChild(t),te(e),I.scan(t),e.open()}function at(e,t){const n=V(e.dataset.id);if(!n)return[];const s=t?.event?.target?.closest?.("td[data-col]"),o=s?re.peek()[Number(s.dataset.col)]:null,l=ye(n.id),i=y.peek().includes(n.id),d=o?yt(o,n.data?.[o.name]):[];return[{header:K(n)},{label:"Edit\u2026",icon:"edit",action:()=>O(n)},{label:"See all of it\u2026",icon:"eye",action:()=>et(n)},{label:"Duplicate",icon:"copy",action:()=>Ze(n)},{label:"Copy id",icon:"hash",action:()=>ge(n.id)},{label:i?"Deselect":"Select",icon:"check",action:()=>de(n.id)},...l.length?[{type:"divider"},...l.map(f=>({label:`${f.trigger?.label||f.title||f.slug}\u2026`,icon:f.trigger?.icon||"zap",disabled:A.peek(),action:()=>Qe(f,n)}))]:[],...o?[{type:"divider"},{label:`Filter by ${o.label||o.name}`,icon:"filter",submenu:[...d.map(f=>({label:f.label,action:()=>Ee(o,f.op,f.value)})),{label:`${o.label||o.name} contains\u2026`,action:async()=>{const f=await E.prompt(`Show entries whose ${D(o.label||o.name)} contains`,{inputPlaceholder:"text"});f&&String(f).trim()&&Ee(o,"contains",String(f).trim())}}]}]:[],...w.peek().length?[{label:`Clear filters (${w.peek().length})`,icon:"x",action:()=>{w.value=[]}}]:[],{type:"divider"},{label:"Delete\u2026",icon:"trash",danger:!0,action:()=>be(n)}]}const L=h.querySelector(".cex-rows"),G=h.querySelector(".cex-head"),pe=h.querySelector(".cex-chips-list");pe.innerHTML=kt;const st=E.loader(h,{type:"dots"});try{p.value=await g.collections.get(a)}catch(e){_.value=`Could not load the collection: ${e.message||e}`}finally{st.destroy()}if(!u())return;if(!p.peek()&&!_.peek()){E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const we=re.peek();L.innerHTML=xt(we.length),G.replaceChildren();const ne=(e,t)=>{const n=document.createElement("th");return e&&(n.className=e),t&&n.append(t),G.appendChild(n),n},$=document.createElement("input");$.type="checkbox",$.title="Select every entry shown",$.setAttribute("aria-label","Select every entry shown"),ne("cex-check",$);const Se=(e,t)=>{const n=document.createElement("button");return n.type="button",n.dataset.sort=e,n.append(document.createTextNode(t),Object.assign(document.createElement("span"),{className:"cex-sort"})),n};we.forEach(e=>ne("",Se(e.name,e.label||e.name))),ne("cex-when",Se("created","Created")),ne("cex-tools"),r(M.effect(()=>{const e=Z.value;G.querySelectorAll("[data-sort]").forEach(t=>{t.querySelector(".cex-sort").textContent=t.dataset.sort===e.key?e.dir==="asc"?"\u25B2":"\u25BC":"",t.parentElement.setAttribute("aria-sort",t.dataset.sort===e.key?e.dir==="asc"?"ascending":"descending":"none")})})),r(M.effect(()=>{const e=T.value.map(n=>n.id),t=new Set(y.value);$.checked=e.length>0&&e.every(n=>t.has(n)),$.indeterminate=!$.checked&&e.some(n=>t.has(n))}));const Ce=e=>{const t=e.target.closest("[data-sort]");if(!t)return;const n=Z.peek();Z.value={key:t.dataset.sort,dir:n.key===t.dataset.sort&&n.dir==="asc"?"desc":"asc"}};G.addEventListener("click",Ce),$.addEventListener("change",()=>{const e=T.peek().map(t=>t.id);y.value=$.checked?[...new Set([...y.peek(),...e])]:y.peek().filter(t=>!e.includes(t))}),r(()=>G.removeEventListener("click",Ce));const ot=M.applyBindings(He,h,{methods:{clearSelection:()=>{y.value=[]},deleteSelected:()=>Xe(),clearAll:()=>Ye(),clearFilters:()=>{w.value=[]},showMore:()=>{T.peek().length>q.peek()?q.value=q.peek()+X:Ke()}}});r(()=>ot.dispose()),r(Oe(h,{position:"bottom"})),r(M.effect(()=>{le.value,w.value,q.value=X}));const $e=e=>{const t=e.target.closest("button[data-idx]");if(!t)return;const n=Number(t.dataset.idx);w.value=w.peek().filter((s,o)=>o!==n)};pe.addEventListener("click",$e),r(()=>pe.removeEventListener("click",$e));const ct=E.contextMenu(L,{cursor:"glyph",match:".cex-row",inherit:!1,density:"compact",items:(e,t)=>at(e,t)});r(()=>ct?.destroy?.());const Ae=e=>{const t=e.target.closest(".cex-row");if(!t)return;if(e.target.closest(".cex-kebab, .cex-run")){Re(e.target.closest(".cex-kebab, .cex-run"));return}if(e.target.closest(".cex-check"))return;const n=V(t.dataset.id);n&&O(n)},Me=e=>{if(!e.target.matches(".cex-sel"))return;const t=e.target.closest(".cex-row");t&&de(t.dataset.id)},Le=e=>{const t=e.target.closest(".cex-row");if(!t||e.target!==t)return;const n=V(t.dataset.id);n&&(e.key==="F10"&&e.shiftKey?(e.preventDefault(),Re(t)):e.key==="Enter"?(e.preventDefault(),O(n)):e.key===" "?(e.preventDefault(),de(n.id)):e.key==="Delete"&&(e.preventDefault(),be(n)))};L.addEventListener("click",Ae),L.addEventListener("change",Me),L.addEventListener("keydown",Le),r(()=>{L.removeEventListener("click",Ae),L.removeEventListener("change",Me),L.removeEventListener("keydown",Le)});const qe=p.peek(),ae=m.get(0).querySelector(".dm-plugin-banner-name")?.querySelector(".dm-plugin-banner-sep");if(ae&&qe?.title){for(;ae.nextSibling;)ae.nextSibling.remove();ae.after(document.createTextNode(qe.title))}z(m,{id:"entries-back",icon:"arrow-left",title:"All collections",onClick:()=>{location.hash="#/collections"}}),z(m,{id:"entries-fields",icon:"edit-3",title:"Edit its fields and settings",onClick:()=>{location.hash=`#/collections/edit/${encodeURIComponent(a)}`}}),bt(a)&&z(m,{id:"entries-form",icon:"layout",title:"Open its form",onClick:()=>{location.hash=`#/forms/edit/${encodeURIComponent(a)}`}}),z(m,{id:"entries-export",icon:"download",title:"Export",onClick:()=>nt()}),z(m,{id:"entries-import",icon:"upload",title:"Import",onClick:()=>tt()}),z(m,{id:"entries-add",icon:"plus",title:"Add entry",onClick:()=>O(null)});const Te=e=>{N?.dirty()&&(e.preventDefault(),e.returnValue="")};if(window.addEventListener("beforeunload",Te),r(()=>window.removeEventListener("beforeunload",Te)),r(()=>{if(N?.dirty())try{S.set(P(a),{id:N.id,values:N.values(),at:new Date().toISOString()})}catch{}ke=!0,[...ue].forEach(e=>{try{e.close(!0)}catch{}})}),I.scan(h),!p.peek()){Y.value=!1;return}if(await Ge(),!u()||(await H(),!u()))return;let C=null;try{C=S.get(P(a))}catch{}if(C?.values){try{S.remove?S.remove(P(a)):S.set(P(a),null)}catch{}let e=C.id?V(C.id):null;if(C.id&&!e&&(e=await g.collections.getEntry(a,C.id).catch(()=>null)),!u()||C.id&&!e)return;const t=e?`your changes to \u201C${D(K(e))}\u201D`:"a new entry",n=e?.meta?.updatedAt&&C.at&&e.meta.updatedAt>C.at?" It has been changed since - check before saving.":"";await E.confirm(`You left ${t} unsaved.${n} Pick up where you left off?`)&&u()&&O(e,C.values)}}};
50
+ </div>`);t.querySelectorAll("[data-fmt]").forEach(n=>n.addEventListener("click",async()=>{const s=n.dataset.fmt;try{const o=await fetch(`/api/collections/${encodeURIComponent(a)}/export?format=${s}`,{headers:{Authorization:`Bearer ${S.get("auth_token")||""}`}});if(!o.ok)throw new Error(`${o.status}`);const l=await o.blob(),i=document.createElement("a");i.href=URL.createObjectURL(l),i.download=`${a}-entries.${s}`,document.body.appendChild(i),i.click(),i.remove(),setTimeout(()=>URL.revokeObjectURL(i.href),1e3),e.close(!0)}catch(o){E.toast(`Export failed (${o.message}).`,{type:"error"})}})),e.element.appendChild(t),te(e),I.scan(t),e.open()}function at(e,t){const n=V(e.dataset.id);if(!n)return[];const s=t?.event?.target?.closest?.("td[data-col]"),o=s?re.peek()[Number(s.dataset.col)]:null,l=ye(n.id),i=y.peek().includes(n.id),u=o?yt(o,n.data?.[o.name]):[];return[{header:K(n)},{label:"Edit\u2026",icon:"edit",action:()=>O(n)},{label:"See all of it\u2026",icon:"eye",action:()=>et(n)},{label:"Duplicate",icon:"copy",action:()=>Ze(n)},{label:"Copy id",icon:"hash",action:()=>ge(n.id)},{label:i?"Deselect":"Select",icon:"check",action:()=>de(n.id)},...l.length?[{type:"divider"},...l.map(f=>({label:`${f.trigger?.label||f.title||f.slug}\u2026`,icon:f.trigger?.icon||"zap",disabled:A.peek(),action:()=>Qe(f,n)}))]:[],...o?[{type:"divider"},{label:`Filter by ${o.label||o.name}`,icon:"filter",submenu:[...u.map(f=>({label:f.label,action:()=>Ee(o,f.op,f.value)})),{label:`${o.label||o.name} contains\u2026`,action:async()=>{const f=await E.prompt(`Show entries whose ${D(o.label||o.name)} contains`,{inputPlaceholder:"text"});f&&String(f).trim()&&Ee(o,"contains",String(f).trim())}}]}]:[],...w.peek().length?[{label:`Clear filters (${w.peek().length})`,icon:"x",action:()=>{w.value=[]}}]:[],{type:"divider"},{label:"Delete\u2026",icon:"trash",danger:!0,action:()=>be(n)}]}const L=h.querySelector(".cex-rows"),G=h.querySelector(".cex-head"),pe=h.querySelector(".cex-chips-list");pe.innerHTML=kt;const st=E.loader(h,{type:"dots"});try{p.value=await g.collections.get(a)}catch(e){_.value=`Could not load the collection: ${e.message||e}`}finally{st.destroy()}if(!r())return;if(!p.peek()&&!_.peek()){E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const we=re.peek();L.innerHTML=xt(we.length),G.replaceChildren();const ne=(e,t)=>{const n=document.createElement("th");return e&&(n.className=e),t&&n.append(t),G.appendChild(n),n},$=document.createElement("input");$.type="checkbox",$.title="Select every entry shown",$.setAttribute("aria-label","Select every entry shown"),ne("cex-check",$);const Se=(e,t)=>{const n=document.createElement("button");return n.type="button",n.dataset.sort=e,n.append(document.createTextNode(t),Object.assign(document.createElement("span"),{className:"cex-sort"})),n};we.forEach(e=>ne("",Se(e.name,e.label||e.name))),ne("cex-when",Se("created","Created")),ne("cex-tools"),d(M.effect(()=>{const e=Z.value;G.querySelectorAll("[data-sort]").forEach(t=>{t.querySelector(".cex-sort").textContent=t.dataset.sort===e.key?e.dir==="asc"?"\u25B2":"\u25BC":"",t.parentElement.setAttribute("aria-sort",t.dataset.sort===e.key?e.dir==="asc"?"ascending":"descending":"none")})})),d(M.effect(()=>{const e=T.value.map(n=>n.id),t=new Set(y.value);$.checked=e.length>0&&e.every(n=>t.has(n)),$.indeterminate=!$.checked&&e.some(n=>t.has(n))}));const Ce=e=>{const t=e.target.closest("[data-sort]");if(!t)return;const n=Z.peek();Z.value={key:t.dataset.sort,dir:n.key===t.dataset.sort&&n.dir==="asc"?"desc":"asc"}};G.addEventListener("click",Ce),$.addEventListener("change",()=>{const e=T.peek().map(t=>t.id);y.value=$.checked?[...new Set([...y.peek(),...e])]:y.peek().filter(t=>!e.includes(t))}),d(()=>G.removeEventListener("click",Ce));const ot=M.applyBindings(He,h,{methods:{clearSelection:()=>{y.value=[]},deleteSelected:()=>Xe(),clearAll:()=>Ye(),clearFilters:()=>{w.value=[]},showMore:()=>{T.peek().length>q.peek()?q.value=q.peek()+X:Ke()}}});d(()=>ot.dispose()),d(Oe(h,{position:"bottom"})),d(M.effect(()=>{le.value,w.value,q.value=X}));const $e=e=>{const t=e.target.closest("button[data-idx]");if(!t)return;const n=Number(t.dataset.idx);w.value=w.peek().filter((s,o)=>o!==n)};pe.addEventListener("click",$e),d(()=>pe.removeEventListener("click",$e));const ct=E.contextMenu(L,{cursor:"glyph",match:".cex-row",inherit:!1,density:"compact",items:(e,t)=>at(e,t)});d(()=>ct?.destroy?.());const Ae=e=>{const t=e.target.closest(".cex-row");if(!t)return;if(e.target.closest(".cex-kebab, .cex-run")){Re(e.target.closest(".cex-kebab, .cex-run"));return}if(e.target.closest(".cex-check"))return;const n=V(t.dataset.id);n&&O(n)},Me=e=>{if(!e.target.matches(".cex-sel"))return;const t=e.target.closest(".cex-row");t&&de(t.dataset.id)},Le=e=>{const t=e.target.closest(".cex-row");if(!t||e.target!==t)return;const n=V(t.dataset.id);n&&(e.key==="F10"&&e.shiftKey?(e.preventDefault(),Re(t)):e.key==="Enter"?(e.preventDefault(),O(n)):e.key===" "?(e.preventDefault(),de(n.id)):e.key==="Delete"&&(e.preventDefault(),be(n)))};L.addEventListener("click",Ae),L.addEventListener("change",Me),L.addEventListener("keydown",Le),d(()=>{L.removeEventListener("click",Ae),L.removeEventListener("change",Me),L.removeEventListener("keydown",Le)});const qe=p.peek(),ae=m.get(0).querySelector(".dm-plugin-banner-name")?.querySelector(".dm-plugin-banner-sep");if(ae&&qe?.title){for(;ae.nextSibling;)ae.nextSibling.remove();ae.after(document.createTextNode(qe.title))}z(m,{id:"entries-back",icon:"arrow-left",title:"All collections",onClick:()=>{location.hash="#/collections"}}),z(m,{id:"entries-fields",icon:"edit-3",title:"Edit its fields and settings",onClick:()=>{location.hash=`#/collections/edit/${encodeURIComponent(a)}`}}),bt(a)&&z(m,{id:"entries-form",icon:"layout",title:"Open its form",onClick:()=>{location.hash=`#/forms/edit/${encodeURIComponent(a)}`}}),z(m,{id:"entries-export",icon:"download",title:"Export",onClick:()=>nt()}),z(m,{id:"entries-import",icon:"upload",title:"Import",onClick:()=>tt()}),z(m,{id:"entries-add",icon:"plus",title:"Add entry",onClick:()=>O(null)});const Te=e=>{N?.dirty()&&(e.preventDefault(),e.returnValue="")};if(window.addEventListener("beforeunload",Te),d(()=>window.removeEventListener("beforeunload",Te)),d(()=>{if(N?.dirty())try{S.set(P(a),{id:N.id,values:N.values(),at:new Date().toISOString()})}catch{}ke=!0,[...ue].forEach(e=>{try{e.close(!0)}catch{}})}),I.scan(h),!p.peek()){Y.value=!1;return}if(await Ge(),!r()||(await H(),!r()))return;let C=null;try{C=S.get(P(a))}catch{}if(C?.values){try{S.remove?S.remove(P(a)):S.set(P(a),null)}catch{}let e=C.id?V(C.id):null;if(C.id&&!e&&(e=await g.collections.getEntry(a,C.id).catch(()=>null)),!r()||C.id&&!e)return;const t=e?`your changes to \u201C${D(K(e))}\u201D`:"a new entry",n=e?.meta?.updatedAt&&C.at&&e.meta.updatedAt>C.at?" It has been changed since - check before saving.":"";await E.confirm(`You left ${t} unsaved.${n} Pick up where you left off?`)&&r()&&O(e,C.values)}}};
@@ -1,8 +1,8 @@
1
- import{apiRequest as ie}from"/admin/js/api.js";import{makeIconInput as Mt}from"/admin/js/lib/shortcode-modal.js";import{enhancePickerFields as et}from"/public/js/forms.js";import{buildWizardSteps as zt}from"/public/js/form-steps.mjs";import{addBannerAction as qe}from"../lib/plugin-chrome.js";import{helpPopovers as Dt}from"../lib/help-popover.js";import{openMenuAt as tt,sidebarCount as Pt}from"../lib/tool-kit.js";import{draftKey as se,draftOf as _t,duplicateField as jt,fieldTitle as $e,fingerprint as nt,leavingEditor as lt,moveField as at,restoreDraft as Vt,snapshotOf as Ae}from"../lib/form-editor-arrange.js";import{fmtDateTime as Gt}from"../lib/dates.js";const ot=[{value:"string",label:"Text (single line)"},{value:"email",label:"Email"},{value:"tel",label:"Phone"},{value:"number",label:"Number"},{value:"textarea",label:"Textarea (multi-line)"},{value:"select",label:"Dropdown (select)"},{value:"radio",label:"Radio buttons"},{value:"checkbox",label:"Single checkbox"},{value:"checkbox-group",label:"Checkbox group"},{value:"chooser",label:"Chooser (visual options)"},{value:"icon",label:"Icon (icon picker)"},{value:"image",label:"Image (media library)"},{value:"date",label:"Date"},{value:"time",label:"Time"},{value:"url",label:"URL"},{value:"password",label:"Password"},{value:"file",label:"File upload"},{value:"hidden",label:"Hidden field"}],Fe=new Set(["select","radio","checkbox-group","chooser"]);let C=[],fe=[],N=null,he=null,ne=null,be={};function q(e,t,o=14){e.textContent="";const n=document.createElement("span");return n.setAttribute("data-icon",t),n.setAttribute("data-icon-size",String(o)),n.style.cssText="display:inline-flex;align-items:center;vertical-align:middle;",e.appendChild(n),window.I?.scan?.(e),e}function re(e,t,o,n=14){return q(e,t,n),e.appendChild(document.createTextNode(" "+o)),e}function Ie(e){const t=document.createElement("span");return t.setAttribute("data-icon","help-circle"),t.setAttribute("data-icon-size","12"),t.style.cssText="display:inline-flex;align-items:center;vertical-align:middle;margin-left:.3rem;opacity:.55;cursor:help;",t.title=e,window.I?.scan?.(t),requestAnimationFrame(()=>{try{t.parentNode&&typeof E<"u"&&E.tooltip&&(t.removeAttribute("title"),E.tooltip(t,{content:e,position:"top"}))}catch{}}),t}let ge=null,it=null,Be=null,ce=null;function Oe(){Be?.(),clearTimeout(it),it=setTimeout(()=>{ge&&ge()},350)}function st(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_|_$/g,"")}function rt(e){return ot.find(t=>t.value===e)?.label||e}function ct(e){return(e||[]).some(t=>t.logic||Array.isArray(t.triggers)&&t.triggers.length)}function dt(e){return e?!!(e.visibility?.conditions?.length||e.requirement?.conditions?.length||e.validation?.length||e.cascade?.sourceField):!1}function Wt(e){const t={...C[e]};if(t.type==="spacer")return t;if(t.type==="page-break"){const h=document.getElementById(`fb-pb-label-${e}`),x=document.getElementById(`fb-pb-desc-${e}`);return h&&(t.label=h.value.trim()||t.label),x&&(t.description=x.value.trim()),t}const o=document.getElementById(`fb-label-${e}`),n=document.getElementById(`fb-name-${e}`),l=document.getElementById(`fb-type-${e}`),a=document.getElementById(`fb-required-${e}`),i=document.getElementById(`fb-placeholder-${e}`),r=document.getElementById(`fb-helper-${e}`),c=document.getElementById(`fb-tooltip-${e}`);if(o&&(t.label=o.value.trim()||t.label),n&&(t.name=n.value.trim()||t.name),l&&(t.type=l.value||t.type),a&&(t.required=a.checked),i&&(t.placeholder=i.value.trim()),r&&(t.helper=r.value.trim()),c&&(t.tooltip=c.value.trim()),t.type==="chooser"){const h=document.getElementById(`fb-chooser-variant-${e}`)?.value;h&&(t.variant=h);const x=document.getElementById(`fb-chooser-multiple-${e}`);x&&(t.multiple=x.checked);const $=document.getElementById(`fb-chooser-density-${e}`)?.value;$&&(t.density=$);const A=document.getElementById(`fb-chooser-columns-${e}`)?.value;A!==""&&A!==void 0&&(t.columns=parseInt(A,10)||3);const G=document.getElementById(`fb-chooser-accent-${e}`)?.value.trim();G?t.accent=G:delete t.accent;const _=document.getElementById(`fb-chooser-accent-style-${e}`)?.value;_?t.accentStyle=_:delete t.accentStyle;const j=document.getElementById(`fb-chooser-glow-${e}`);j&&(t.glow=j.checked);const I=document.getElementById(`fb-chooser-glow-colour-${e}`)?.value.trim();I?t.glowColour=I:delete t.glowColour;const X=document.getElementById(`fb-chooser-shadow-${e}`)?.value;X&&X!=="none"?t.shadow=X:delete t.shadow;const H=document.getElementById(`fb-chooser-shadow-colour-${e}`)?.value.trim();H?t.shadowColour=H:delete t.shadowColour,we(e),C[e]&&Array.isArray(C[e].options)&&(t.options=C[e].options)}else if(Fe.has(t.type)){["variant","density","columns","accent","accentStyle","glow","glowColour","shadow","shadowColour"].forEach(x=>delete t[x]),t.type!=="checkbox-group"&&t.type!=="multiselect"&&delete t.multiple;const h=document.getElementById(`fb-options-${e}`);h&&(t.options=h.value.split(`
2
- `).filter(x=>x.trim()).map(x=>{const[$,...A]=x.split(":");return{value:$.trim(),label:A.join(":").trim()||$.trim()}}))}if(t.type==="textarea"){const h=parseInt(document.getElementById(`fb-rows-${e}`)?.value,10);h>0&&(t.formConfig={...t.formConfig||{},rows:h})}const s=document.getElementById(`fb-span-${e}`),d=document.getElementById(`fb-fullwidth-${e}`);if(s||d){const h={...t.formConfig||{}};if(d?.checked)h.span="full";else{const x=parseInt(s?.value,10);x>1?h.span=x:delete h.span}t.formConfig=Object.keys(h).length?h:void 0}const u=document.getElementById(`fb-minlength-${e}`)?.value;u&&(t.minLength=parseInt(u,10));const b=document.getElementById(`fb-maxlength-${e}`)?.value;b&&(t.maxLength=parseInt(b,10));const y=document.getElementById(`fb-min-${e}`)?.value;y!==""&&y!==void 0&&(t.min=parseFloat(y));const m=document.getElementById(`fb-max-${e}`)?.value;m!==""&&m!==void 0&&(t.max=parseFloat(m));const f=Ht(e);f?t.logic=f:delete t.logic;const k=cn(e);return k?t.triggers=k:delete t.triggers,t}function Ht(e){const t=document.querySelector(`.fb-field-card[data-index="${e}"]`);if(!t)return;const o=t.querySelector(".fb-field-logic");if(!o)return;const n={};let l=!1;const a=o.querySelector('[data-logic-section="visibility"]');if(a){const s=a.querySelector(".fb-logic-vis-default"),d=a.querySelector(".fb-logic-vis-transition"),u=Array.from(a.querySelectorAll(".fb-logic-cond-row")).map(f=>{const k=f.querySelector(".fb-logic-cond-field"),h=f.querySelector(".fb-logic-cond-op"),x=f.querySelector(".fb-logic-cond-val"),$=f.querySelector(".fb-logic-vis-then");return k?.value?{when:{all:[{field:k.value,operator:h.value,value:x.value}]},then:$.value}:null}).filter(Boolean),b=s?.value||"visible",y=d?.value||"none",m=parseInt(a.querySelector(".fb-logic-vis-duration")?.value||"",10);(b!=="visible"||u.length>0||y!=="none")&&(n.visibility={default:b,conditions:u},y!=="none"&&(n.visibility.transition=y),y!=="none"&&Number.isFinite(m)&&(n.visibility.duration=m),l=!0)}const i=o.querySelector('[data-logic-section="requirement"]');if(i){const s=i.querySelector(".fb-logic-req-default"),d=Array.from(i.querySelectorAll(".fb-logic-cond-row")).map(u=>{const b=u.querySelector(".fb-logic-cond-field"),y=u.querySelector(".fb-logic-cond-op"),m=u.querySelector(".fb-logic-cond-val"),f=u.querySelector(".fb-logic-req-then");return b?.value?{when:{all:[{field:b.value,operator:y.value,value:m.value}]},then:f.value==="true"}:null}).filter(Boolean);d.length>0&&(n.requirement={default:s?.checked===!0,conditions:d},l=!0)}const r=o.querySelector('[data-logic-section="validation"]');if(r){const s=Array.from(r.querySelectorAll(".fb-logic-val-rule")).map(d=>{const u=d.querySelector(".fb-logic-val-type"),b=d.querySelector(".fb-logic-val-pattern"),y=d.querySelector(".fb-logic-val-flags"),m=d.querySelector(".fb-logic-val-message");if(!b?.value.trim())return null;const f=u?.value||"regex",k={type:f,message:m?.value.trim()||"Invalid value."};return f==="regex"?(k.pattern=b.value.trim(),y?.value.trim()&&(k.flags=y.value.trim())):k.field=b.value.trim(),k}).filter(Boolean);s.length>0&&(n.validation=s,l=!0)}const c=o.querySelector('[data-logic-section="cascade"]');if(c){const s=c.querySelector(".fb-logic-cascade-source"),d=c.querySelector(".fb-logic-cascade-mapping"),u=c.querySelector(".fb-logic-cascade-defaults"),b=s?.value?.trim();if(b){let y={};try{y=JSON.parse(d?.value||"{}")}catch{}const m=(u?.value||"").split(`
3
- `).filter(f=>f.trim()).map(f=>{const[k,...h]=f.split(":");return{value:k.trim(),label:h.join(":").trim()||k.trim()}});n.cascade={sourceField:b,mapping:y,defaultOptions:m},l=!0}}return l?n:void 0}function z(){C=C.map((e,t)=>Wt(t))}function Ut(e){const t=e.firstElementChild;if(!t||t.querySelector(".fb-drag-grip"))return;const o=document.createElement("span");o.className="fb-drag-grip",o.setAttribute("data-icon","grip-vertical"),o.setAttribute("data-icon-size","16"),o.title="Drag to reorder",t.insertBefore(o,t.firstChild),window.I?.scan?.(t)}function Q(e){const t=e.find("#fields-list").get(0),o=e.find("#fields-empty-msg").get(0);if(t){if(Array.from(t.querySelectorAll(".fb-field-card")).forEach(n=>n.remove()),C.length===0){o&&(o.style.display=""),ce?.(t),Oe();return}o&&(o.style.display="none"),C.forEach((n,l)=>{const a=n.type==="page-break"?Jt(n,l,e):n.type==="spacer"?Yt(n,l,e):Kt(n,l,e);Ut(a),t.appendChild(a)}),ce?.(t),Oe()}}function Jt(e,t,o){const n=document.createElement("div");n.className="fb-field-card",n.dataset.index=t,n.style.cssText="border:2px dashed var(--dm-border);border-radius:8px;overflow:hidden;margin-bottom:.75rem;background:var(--dm-card-bg);";const l=document.createElement("div");l.style.cssText="display:flex;align-items:center;gap:.6rem;padding:.6rem .8rem;cursor:pointer;user-select:none;";const a=document.createElement("span");a.textContent="- Page Break -",a.style.cssText="font-size:.7rem;padding:.15rem .5rem;border-radius:999px;background:color-mix(in srgb, var(--dm-text-muted) 20%, transparent);color:var(--dm-text-muted);white-space:nowrap;flex-shrink:0;font-style:italic;";const i=document.createElement("span");i.textContent=e.label||"Untitled Step",i.style.cssText="flex:1;font-weight:600;font-size:.9rem;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dm-text-muted);";const r=document.createElement("div");r.style.cssText="display:flex;gap:.25rem;flex-shrink:0;margin-left:.5rem;";const c=document.createElement("button");c.className="btn btn-xs btn-ghost",c.title="Edit step",q(c,"more-horizontal"),c.addEventListener("click",y=>{y.stopPropagation(),d.style.display=d.style.display==="none"?"":"none"}),r.appendChild(c);const s=document.createElement("button");s.className="btn btn-xs btn-ghost",s.style.cssText="line-height:1;opacity:.6;",s.title="Remove page break",q(s,"x"),s.addEventListener("click",async y=>{y.stopPropagation(),await E.confirm("Remove this page break?")&&(z(),C.splice(t,1),Q(o))}),r.appendChild(s),l.appendChild(a),l.appendChild(i),l.appendChild(r),l.addEventListener("click",()=>{d.style.display=d.style.display==="none"?"":"none"});const d=document.createElement("div");d.className="fb-field-body",d.style.cssText="padding:.8rem;border-top:1px dashed var(--dm-border);display:none;";const u=D([P("Step Title",`fb-pb-label-${t}`,"text",e.label||"","Shown as the wizard step heading"),P("Step Description",`fb-pb-desc-${t}`,"text",e.description||"","Optional sub-heading")]),b=u.querySelector(`#fb-pb-label-${t}`);return b&&b.addEventListener("input",()=>{i.textContent=b.value||"Untitled Step"}),d.appendChild(u),n.appendChild(l),n.appendChild(d),n}function Yt(e,t,o){const n=document.createElement("div");n.className="fb-field-card",n.dataset.index=t,n.style.cssText="border:1px dashed var(--dm-border);border-radius:6px;margin-bottom:.5rem;background:transparent;";const l=document.createElement("div");l.style.cssText="display:flex;align-items:center;gap:.6rem;padding:.4rem .8rem;";const a=document.createElement("div");a.style.cssText="flex:1;height:1px;background:var(--dm-border);";const i=document.createElement("span");i.textContent="Spacer",i.style.cssText="font-size:.7rem;color:var(--dm-text-muted);white-space:nowrap;padding:0 .4rem;font-style:italic;";const r=document.createElement("div");r.style.cssText="flex:1;height:1px;background:var(--dm-border);";const c=document.createElement("div");c.style.cssText="display:flex;gap:.25rem;flex-shrink:0;";const s=document.createElement("button");return s.className="btn btn-xs btn-ghost",s.style.cssText="line-height:1;opacity:.6;",s.title="Remove spacer",q(s,"x"),s.addEventListener("click",async d=>{d.stopPropagation(),z(),C.splice(t,1),Q(o)}),c.appendChild(s),l.appendChild(a),l.appendChild(i),l.appendChild(r),l.appendChild(c),n.appendChild(l),n}function Kt(e,t,o){const n=document.createElement("div");n.className="fb-field-card",n.dataset.index=t,n.style.cssText="border:1px solid var(--dm-border);border-radius:8px;overflow:hidden;margin-bottom:.75rem;background:var(--dm-card-bg);box-shadow:var(--dm-shadow-sm);";const l=document.createElement("div");l.style.cssText="display:flex;align-items:center;gap:.6rem;padding:.65rem .85rem;background:var(--dm-card-header-bg);cursor:pointer;user-select:none;";const a=document.createElement("span");a.textContent=rt(e.type),a.style.cssText="font-size:.7rem;padding:.15rem .45rem;border-radius:999px;background:color-mix(in srgb, var(--dm-primary) 14%, transparent);color:var(--dm-primary);white-space:nowrap;flex-shrink:0;";const i=document.createElement("span");i.textContent=e.label||"(unlabelled)",i.style.cssText="flex:1;font-weight:600;font-size:.9rem;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;";const r=document.createElement("div");r.style.cssText="display:flex;gap:.25rem;flex-shrink:0;margin-left:.5rem;";const c=document.createElement("button");c.className="btn btn-xs btn-ghost",c.title="Edit field",q(c,"more-horizontal"),c.addEventListener("click",u=>{u.stopPropagation(),d.style.display=d.style.display==="none"?"":"none"}),r.appendChild(c);const s=document.createElement("button");if(s.className="btn btn-xs btn-ghost",s.style.cssText="line-height:1;opacity:.6;",s.title="Remove field",q(s,"x"),s.addEventListener("click",async u=>{u.stopPropagation(),await E.confirm("Remove this field?")&&(z(),C.splice(t,1),Q(o))}),r.appendChild(s),l.appendChild(a),l.appendChild(i),e.required){const u=document.createElement("span");u.textContent="required",u.style.cssText="font-size:.7rem;color:var(--dm-danger);flex-shrink:0;",l.appendChild(u)}if(dt(e.logic)){const u=document.createElement("span");q(u,"zap"),u.title="Has conditional logic",u.style.cssText="font-size:.75rem;color:var(--dm-primary);flex-shrink:0;",l.appendChild(u)}l.appendChild(r),l.addEventListener("click",()=>{d.style.display=d.style.display==="none"?"":"none"});const d=Qt(e,t,i);return d.style.display="none",n.appendChild(l),n.appendChild(d),n}function Qt(e,t,o){const n=document.createElement("div");n.className="fb-field-body",n.style.cssText="padding:1rem .9rem;border-top:1px solid var(--dm-border);";const l=D([P("Label",`fb-label-${t}`,"text",e.label||"","Shown above the field"),P("Field Name",`fb-name-${t}`,"text",e.name||"","Used as data key")]),a=D([bn("Type",`fb-type-${t}`,ot,e.type||"string"),Nt("Required",`fb-required-${t}`,e.required||!1)]),i=D([P("Placeholder",`fb-placeholder-${t}`,"text",e.placeholder||"","Hint text inside the field")]),r=D([P("Helper Text",`fb-helper-${t}`,"text",e.helper||"","Shown below the field"),P("Tooltip",`fb-tooltip-${t}`,"text",e.tooltip||"","Shown on hover via a help icon next to the label")]),c=e.formConfig?.span,s=D([P("Column Span",`fb-span-${t}`,"number",c&&c!=="full"?String(c):"1","Columns to span (grid only)"),Nt("Full Width",`fb-fullwidth-${t}`,c==="full")]);s.classList.add("fb-grid-row"),s.style.display=document.getElementById("setting-layout")?.value==="grid"?"flex":"none",n.appendChild(l),n.appendChild(a),n.appendChild(i),n.appendChild(r),n.appendChild(s);const d=n.querySelector(`#fb-label-${t}`),u=n.querySelector(`#fb-name-${t}`);d&&d.addEventListener("input",()=>{o&&(o.textContent=d.value||"(unlabelled)"),u&&!u.dataset.manuallyEdited&&(u.value=st(d.value))}),u&&u.addEventListener("input",()=>{u.dataset.manuallyEdited="1"});const b=n.querySelector(`#fb-type-${t}`);b&&b.addEventListener("change",()=>{const m=n.closest(".fb-field-card");if(m){const h=m.querySelector("span");h&&(h.textContent=rt(b.value))}const f=n.querySelector(".fb-field-extras");f&&f.remove();const k=wt(b.value,e,t);k&&n.appendChild(k)});const y=wt(e.type,e,t);return y&&n.appendChild(y),n.appendChild(nn(e,t)),n.appendChild(sn(e,t)),n}const ye=[{value:"equals",label:"equals"},{value:"not_equals",label:"does not equal"},{value:"contains",label:"contains"},{value:"not_contains",label:"does not contain"},{value:"starts_with",label:"starts with"},{value:"ends_with",label:"ends with"},{value:"greater_than",label:"is greater than"},{value:"less_than",label:"is less than"},{value:"is_empty",label:"is empty"},{value:"is_not_empty",label:"is not empty"},{value:"in",label:"is one of (comma sep)"},{value:"not_in",label:"is not one of (comma sep)"},{value:"matches_regex",label:"matches regex"}],Re=new Set(["is_empty","is_not_empty"]),pt={equals:"includes",not_equals:"does not include",contains:"any value contains",not_contains:"no value contains",starts_with:"any value starts with",ends_with:"any value ends with",greater_than:"any value is greater than",less_than:"any value is less than",is_empty:"nothing selected",is_not_empty:"something selected",in:"includes one of (comma sep)",not_in:"includes none of (comma sep)",matches_regex:"any value matches regex"};function Me(e){return!!e&&(e.type==="checkbox-group"||!!e.multiple)}function ve(e){const t=document.createElement("p");return t.textContent=e,t.style.cssText="font-size:.75rem;font-weight:700;color:var(--dm-text-muted);margin:0 0 .7rem;text-transform:uppercase;letter-spacing:.06em;",t}function xe(e,t,o,n,l){const a=document.createElement("div");a.className="fb-logic-cond-row",a.style.cssText="display:flex;gap:.55rem;align-items:center;margin-bottom:.6rem;flex-wrap:wrap;";const i=document.createElement("span");i.textContent="When",i.style.cssText="font-size:.73rem;color:var(--dm-text-muted);flex-shrink:0;";const r=document.createElement("select");r.className="form-select fb-logic-cond-field",r.style.cssText="flex:1 1 140px;min-width:140px;",t.forEach(m=>{const f=document.createElement("option");f.value=m.value,f.textContent=m.label,e&&m.value===e.field&&(f.selected=!0),r.appendChild(f)});const c=document.createElement("select");c.className="form-select fb-logic-cond-op",c.style.cssText="flex:1 1 160px;min-width:140px;",ye.forEach(m=>{const f=document.createElement("option");f.value=m.value,f.textContent=m.label,e&&m.value===e.operator&&(f.selected=!0),c.appendChild(f)});const s=()=>{const m=t.find(f=>f.value===r.value);Array.from(c.options).forEach(f=>{const k=ye.find(h=>h.value===f.value);f.textContent=m?.multi&&pt[f.value]||k?.label||f.value})};s(),r.addEventListener("change",s);const d=document.createElement("input");d.type="text",d.className="form-input fb-logic-cond-val",d.placeholder="value",d.style.cssText="flex:1 1 120px;min-width:100px;",d.value=e?.value||"",e&&Re.has(e.operator)&&(d.style.display="none"),c.addEventListener("change",()=>{d.style.display=Re.has(c.value)?"none":""});const u=document.createElement("span");q(u,"arrow-right"),u.style.cssText="font-size:.73rem;color:var(--dm-text-muted);flex-shrink:0;";const b=document.createElement("select");b.className=`form-select ${n}`,b.style.cssText="flex:1 1 140px;min-width:120px;",o.forEach(m=>{const f=document.createElement("option");f.value=m.value,f.textContent=m.label,m.value===l&&(f.selected=!0),b.appendChild(f)});const y=document.createElement("button");return y.type="button",y.className="btn btn-xs btn-ghost",y.style.cssText="line-height:1;opacity:.6;",q(y,"x"),y.style.flexShrink="0",y.addEventListener("click",()=>a.remove()),a.appendChild(i),a.appendChild(r),a.appendChild(c),a.appendChild(d),a.appendChild(u),a.appendChild(b),a.appendChild(y),a}function Xt(e,t,o){const n=document.createElement("div");n.dataset.logicSection="visibility",n.appendChild(ve("Visibility"));const l=document.createElement("div");l.style.cssText="display:flex;align-items:center;gap:.6rem;margin-bottom:.65rem;flex-wrap:wrap;";const a=document.createElement("span");a.textContent="Default:",a.style.cssText="font-size:.8rem;flex-shrink:0;";const i=document.createElement("select");i.className="form-select fb-logic-vis-default",i.style.cssText="max-width:240px;",[{value:"visible",label:"Visible"},{value:"hidden",label:"Hidden"}].forEach(h=>{const x=document.createElement("option");x.value=h.value,x.textContent=h.label,h.value===(e.default||"visible")&&(x.selected=!0),i.appendChild(x)}),l.appendChild(a),l.appendChild(i),n.appendChild(l);const r=document.createElement("div");r.style.cssText="display:flex;align-items:center;gap:.6rem;margin-bottom:.65rem;flex-wrap:wrap;";const c=document.createElement("span");c.textContent="Transition:",c.style.cssText="font-size:.8rem;flex-shrink:0;";const s=document.createElement("select");s.className="form-select fb-logic-vis-transition",s.style.cssText="max-width:240px;",[{value:"none",label:"None (instant)"},{value:"fade",label:"Fade"},{value:"slide",label:"Slide"},{value:"scale",label:"Scale"}].forEach(h=>{const x=document.createElement("option");x.value=h.value,x.textContent=h.label,h.value===(e.transition||"none")&&(x.selected=!0),s.appendChild(x)}),r.appendChild(c),r.appendChild(s),n.appendChild(r);const d=document.createElement("div");d.style.cssText="display:flex;align-items:center;gap:.6rem;margin-bottom:.65rem;flex-wrap:wrap;";const u=document.createElement("span");u.textContent="Speed:",u.style.cssText="font-size:.8rem;flex-shrink:0;";const b=document.createElement("select");b.className="form-select fb-logic-vis-duration",b.style.cssText="max-width:240px;",[{value:"",label:"Default"},{value:"120",label:"Very fast (120ms)"},{value:"200",label:"Fast (200ms)"},{value:"350",label:"Medium (350ms)"},{value:"600",label:"Slow (600ms)"},{value:"1000",label:"Very slow (1s)"}].forEach(h=>{const x=document.createElement("option");x.value=h.value,x.textContent=h.label,h.value===String(e.duration??"")&&(x.selected=!0),b.appendChild(x)}),d.appendChild(u),d.appendChild(b),n.appendChild(d);const y=()=>{d.style.display=s.value==="none"?"none":""};y(),s.addEventListener("change",y);const m=document.createElement("div");m.className="fb-logic-vis-rules";const f=o.map(h=>({value:h.name,label:h.label||h.name,multi:Me(h)})),k=[{value:"visible",label:"Show"},{value:"hidden",label:"Hide"}];if((e.conditions||[]).forEach(h=>{const x=(h.when?.all||h.when?.any||[])[0],$=h.then==="hidden"?"hidden":"visible";f.length>0&&m.appendChild(xe(x,f,k,"fb-logic-vis-then",$))}),n.appendChild(m),f.length>0){const h=document.createElement("button");h.type="button",h.className="btn btn-xs btn-ghost",h.style.cssText="font-size:.73rem;margin-top:.35rem;",re(h,"plus","Add visibility rule"),h.addEventListener("click",()=>m.appendChild(xe(null,f,k,"fb-logic-vis-then","visible"))),n.appendChild(h)}return n}function Zt(e,t,o){const n=document.createElement("div");n.dataset.logicSection="requirement",n.appendChild(ve("Conditional Requirement"));const l=document.createElement("label");l.style.cssText="display:flex;align-items:center;gap:.5rem;font-size:.8rem;cursor:pointer;margin-bottom:.65rem;";const a=document.createElement("input");a.type="checkbox",a.className="fb-logic-req-default",a.checked=e.default===!0,l.appendChild(a),l.appendChild(document.createTextNode("Required by default")),n.appendChild(l);const i=document.createElement("div");i.className="fb-logic-req-rules";const r=o.map(s=>({value:s.name,label:s.label||s.name,multi:Me(s)})),c=[{value:"true",label:"Make required"},{value:"false",label:"Make optional"}];if((e.conditions||[]).forEach(s=>{const d=(s.when?.all||s.when?.any||[])[0],u=s.then===!0?"true":"false";r.length>0&&i.appendChild(xe(d,r,c,"fb-logic-req-then",u))}),n.appendChild(i),r.length>0){const s=document.createElement("button");s.type="button",s.className="btn btn-xs btn-ghost",s.style.cssText="font-size:.73rem;margin-top:.35rem;",re(s,"plus","Add requirement rule"),s.addEventListener("click",()=>i.appendChild(xe(null,r,c,"fb-logic-req-then","true"))),n.appendChild(s)}return n}function ut(e){const t=document.createElement("div");t.className="fb-logic-val-rule",t.style.cssText="display:flex;gap:.55rem;align-items:center;margin-bottom:.6rem;flex-wrap:wrap;";const o=document.createElement("select");o.className="form-select fb-logic-val-type",o.style.cssText="flex:0 0 140px;",[{value:"regex",label:"Regex"},{value:"match",label:"Match field"}].forEach(r=>{const c=document.createElement("option");c.value=r.value,c.textContent=r.label,r.value===(e?.type||"regex")&&(c.selected=!0),o.appendChild(c)});const n=document.createElement("input");n.type="text",n.className="form-input fb-logic-val-pattern",n.placeholder=e?.type==="match"?"field name":"pattern",n.value=e?.pattern||e?.field||"",n.style.cssText="flex:3;";const l=document.createElement("input");l.type="text",l.className="form-input fb-logic-val-flags",l.placeholder="flags",l.value=e?.flags||"",l.style.cssText="flex:0 0 55px;",e?.type==="match"&&(l.style.display="none"),o.addEventListener("change",()=>{l.style.display=o.value==="match"?"none":"",n.placeholder=o.value==="match"?"field name":"pattern"});const a=document.createElement("input");a.type="text",a.className="form-input fb-logic-val-message",a.placeholder="Error message",a.value=e?.message||"",a.style.cssText="flex:4;";const i=document.createElement("button");return i.type="button",i.className="btn btn-xs btn-ghost",i.style.cssText="line-height:1;opacity:.6;",q(i,"x"),i.style.flexShrink="0",i.addEventListener("click",()=>t.remove()),t.appendChild(o),t.appendChild(n),t.appendChild(l),t.appendChild(a),t.appendChild(i),t}function en(e,t,o){const n=document.createElement("div");n.dataset.logicSection="validation",n.appendChild(ve("Custom Validation"));const l=document.createElement("div");l.className="fb-logic-val-rules",(e||[]).forEach(i=>l.appendChild(ut(i))),n.appendChild(l);const a=document.createElement("button");return a.type="button",a.className="btn btn-xs btn-ghost",a.style.cssText="font-size:.73rem;margin-top:.35rem;",re(a,"plus","Add validation rule"),a.addEventListener("click",()=>l.appendChild(ut(null))),n.appendChild(a),n}function tn(e,t,o){const n=document.createElement("div");n.dataset.logicSection="cascade",n.appendChild(ve("Cascade Options")),n.lastChild.appendChild(Ie("Make this field's options depend on another field. When the source field changes, the options here are replaced by the mapping entry for its value; if nothing matches, the default options below are shown."));const l=document.createElement("div");l.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem;";const a=document.createElement("span");a.textContent="Source field:",a.style.cssText="font-size:.8rem;flex-shrink:0;";const i=document.createElement("select");i.className="form-select fb-logic-cascade-source",i.style.cssText="flex:1;";const r=document.createElement("option");r.value="",r.textContent="- none -",i.appendChild(r),o.forEach(b=>{const y=document.createElement("option");y.value=b.name,y.textContent=b.label||b.name,b.name===e.sourceField&&(y.selected=!0),i.appendChild(y)}),l.appendChild(a),l.appendChild(i),n.appendChild(l);const c=document.createElement("p");c.textContent='Mapping JSON - {"value":[{"value":"...","label":"..."}]}',c.appendChild(Ie(`Each key is a value of the source field; its list becomes this field's options when that value is picked. Example: {"uk":[{"value":"london","label":"London"}]} shows London when the source is "uk".`)),c.style.cssText="font-size:.73rem;color:var(--dm-text-muted);margin:.3rem 0 .2rem;";const s=document.createElement("textarea");s.className="form-input fb-logic-cascade-mapping",s.rows=4,s.style.cssText="font-family:monospace;",s.placeholder='{"uk": [{"value": "london", "label": "London"}]}',s.value=e.mapping?JSON.stringify(e.mapping,null,2):"",n.appendChild(c),n.appendChild(s);const d=document.createElement("p");d.textContent="Default options (one per line: value:Label)",d.appendChild(Ie("Shown before the source field has a value, or when its value has no mapping entry.")),d.style.cssText="font-size:.73rem;color:var(--dm-text-muted);margin:.3rem 0 .2rem;";const u=document.createElement("textarea");return u.className="form-input fb-logic-cascade-defaults",u.rows=3,u.style.cssText="font-family:monospace;",u.placeholder=`option1:Option 1
4
- option2:Option 2`,u.value=(e.defaultOptions||[]).map(b=>{const y=typeof b=="string"?b:b.value??"",m=typeof b=="string"?b:b.label??y;return y===m?y:`${y}:${m}`}).join(`
5
- `),n.appendChild(d),n.appendChild(u),n}function nn(e,t){const o=e.logic||{},n=C.filter((u,b)=>b!==t&&u.type!=="page-break"&&u.type!=="spacer"),l=document.createElement("div");l.className="fb-field-logic",l.style.cssText="margin-top:.75rem;border-top:1px solid var(--dm-border);padding-top:.5rem;";const a=document.createElement("div");a.style.cssText="display:flex;align-items:center;justify-content:space-between;cursor:pointer;padding:.15rem 0;";const i=document.createElement("span");i.style.cssText="font-size:.8rem;font-weight:600;color:var(--dm-text-muted);",i.textContent="\u26A1 Conditional Logic";const r=dt(o),c=document.createElement("button");c.type="button",c.className="btn btn-xs btn-ghost",q(c,r?"chevron-down":"chevron-right",12);const s=document.createElement("div");s.className="fb-logic-body",s.style.cssText="padding:.25rem 0 .25rem;"+(r?"":"display:none;"),a.addEventListener("click",()=>{const u=s.style.display==="none";s.style.display=u?"":"none",q(c,u?"chevron-down":"chevron-right",12)}),a.appendChild(i),a.appendChild(c),l.appendChild(a),s.appendChild(Xt(o.visibility||{},t,n)),s.appendChild(Zt(o.requirement||{},t,n)),s.appendChild(en(o.validation||[],t,n));const d=document.getElementById(`fb-type-${t}`)?.value||e.type;return Fe.has(d)&&s.appendChild(tn(o.cascade||{},t,n)),l.appendChild(s),l}const de=[{value:"banner",label:"Show a banner",kind:"state",params:["message","level","position","dismissible"],placeholder:"e.g. You must be 18 or over to continue."},{value:"toast",label:"Show a toast",kind:"event",params:["message","level"],placeholder:"e.g. Thanks - carry on."},{value:"hide-fields",label:"Hide fields",kind:"state",params:["target"]},{value:"disable-fields",label:"Disable fields",kind:"state",params:["target"]},{value:"require-fields",label:"Make fields required",kind:"state",params:["target"]},{value:"block-submit",label:"Block submission",kind:"state",params:["message"],messageLabel:"Reason, shown under the button",placeholder:'Blank: "This form cannot be submitted yet."'},{value:"end-form",label:"End the form here",kind:"state",params:["message","finish","finishLabel","doneMessage","record"],messageLabel:"Message shown in place of the rest",placeholder:"e.g. Sorry - you must be 18 or over to apply."},{value:"goto-step",label:"Jump to a wizard step",kind:"event",params:["step"]},{value:"redirect",label:"Redirect after submit",kind:"event",params:["url"]},{value:"celebrate",label:"Celebrate",kind:"event",params:["theme","intensity"]},{value:"run-action",label:"Run a CMS action",kind:"event",params:["slug"]},{value:"notify",label:"Notify the admins",kind:"event",params:["message","level"],messageLabel:"Notification title",placeholder:"Blank: titled after the form",noOnce:!0,kindText:"when the form is submitted"}],Ce=[{value:"info",label:"Info"},{value:"success",label:"Success"},{value:"warning",label:"Warning"},{value:"error",label:"Error"}],mt=[{value:"field-below",label:"Below this field"},{value:"field-above",label:"Above this field"},{value:"form-top",label:"Top of the form"}],ft=[{value:"rest",label:"The rest of the form"},{value:"all",label:"Every other field"},{value:"listed",label:"Chosen fields"}],ht=[{value:"auto",label:"Whatever is in season"},{value:"christmas",label:"Christmas"},{value:"halloween",label:"Halloween"},{value:"valentines",label:"Valentine's Day"},{value:"guy-fawkes",label:"Guy Fawkes Night"},{value:"st-patricks",label:"St Patrick's Day"},{value:"st-andrews",label:"St Andrew's Day"},{value:"st-davids",label:"St David's Day"},{value:"st-georges",label:"St George's Day"}];function bt(e,t=!1){const o=(i,r)=>({value:i.name,label:`${i.label||i.name}${r?" (this field)":""}`,multi:Me(i)}),n=i=>i&&i.type!=="page-break"&&i.type!=="spacer"&&i.name,l=C.filter((i,r)=>r!==e&&n(i)).map(i=>o(i,!1)),a=C[e];return t&&n(a)?[o(a,!0),...l]:l}function J(e,t,o){const n=document.createElement("select");return n.className=`form-select ${e}`,t.forEach(l=>{const a=document.createElement("option");a.value=l.value,a.textContent=l.label,l.value===o&&(a.selected=!0),n.appendChild(a)}),n}function le(e,t,o){const n=document.createElement("input");return n.type="text",n.className=`form-input ${e}`,n.value=t==null?"":String(t),o&&(n.placeholder=o),n}function ze(e,t){const o=document.createElement("button");return o.type="button",o.className="btn btn-xs btn-ghost fb-trig-remove",t&&(o.title=t,o.setAttribute("aria-label",t)),q(o,"x"),o.addEventListener("click",n=>{n.stopPropagation(),e()}),o}function V(e,t,{full:o=!1,hint:n=""}={}){const l=document.createElement("label");l.className=`fb-trig-f${o?" fb-trig-f--full":""}`;const a=document.createElement("span");if(a.className="fb-trig-f-label",a.textContent=e,l.appendChild(a),l.appendChild(t),n){const i=document.createElement("span");i.className="fb-trig-f-hint",i.textContent=n,l.appendChild(i)}return l}function pe(e,t,o,n){const l=document.createElement("label");l.className="fb-trig-check",n&&(l.title=n);const a=document.createElement("input");return a.type="checkbox",a.className=`form-check ${e}`,a.checked=!!t,l.appendChild(a),l.appendChild(document.createTextNode(o)),l}function gt(e,t){const o=document.createElement("div");o.className="fb-trig-cond-row";const n=J("fb-trig-cond-field",t,e?.field),l=J("fb-trig-cond-op",ye,e?.operator),a=le("fb-trig-cond-val",e?.value,"value"),i=()=>{const c=t.find(s=>s.value===n.value);Array.from(l.options).forEach(s=>{const d=ye.find(u=>u.value===s.value);s.textContent=c?.multi&&pt[s.value]||d?.label||s.value})},r=()=>{a.hidden=Re.has(l.value)};return i(),r(),n.addEventListener("change",i),l.addEventListener("change",r),o.appendChild(n),o.appendChild(l),o.appendChild(a),o.appendChild(ze(()=>o.remove(),"Remove condition")),o}function yt(e,t){const o=document.createElement("div");o.className="fb-trig-params";const n=de.find(c=>c.value===(e.type||"banner"));if(!n)return o;const l=document.createElement("div");l.className="fb-trig-grid";const a=document.createElement("div");a.className="fb-trig-opts";const i=[];for(const c of n.params)switch(c){case"message":l.appendChild(V(n.messageLabel||"Message",le("fb-trig-p-message",e.message,n.placeholder||""),{full:!0}));break;case"finish":a.appendChild(pe("fb-trig-p-finish-on",e.finish!==!1,"Let the visitor finish (send the answers given so far)",'Untick to stop the form dead: the submit button is disabled and nothing is sent, so nothing is saved and "Notify the admins" cannot fire.'));break;case"finishLabel":{const s=V("Button text",le("fb-trig-p-finish",e.finishLabel,"Finish"));i.push(s),l.appendChild(s);break}case"doneMessage":{const s=V("Closing message",le("fb-trig-p-done",e.doneMessage,"Blank: the form's own success message"));i.push(s),l.appendChild(s);break}case"record":{const s=pe("fb-trig-p-record",e.record!==!1,"Save the answers given so far",`Untick to keep nothing: no submission is stored and the form's email, webhook and actions do not run. A "Notify the admins" action still fires.`);i.push(s),a.appendChild(s);break}case"level":l.appendChild(V(n.value==="notify"?"Severity":"Level",J("fb-trig-p-level",Ce,e.level||"info")));break;case"position":l.appendChild(V("Where",J("fb-trig-p-position",mt,e.position||"field-below")));break;case"dismissible":a.appendChild(pe("fb-trig-p-dismissible",e.dismissible!==!1,"The visitor can dismiss it"));break;case"target":{const s=J("fb-trig-p-target",ft,e.target||(e.fields?.length?"listed":"rest")),d=document.createElement("select");d.className="form-select fb-trig-p-fields",d.multiple=!0,d.size=4;const u=new Set(e.fields||[]);bt(t).forEach(m=>{const f=document.createElement("option");f.value=m.value,f.textContent=m.label,u.has(m.value)&&(f.selected=!0),d.appendChild(f)});const b=V("Fields",d,{hint:"Ctrl/Cmd-click to pick several"}),y=()=>{b.hidden=s.value!=="listed"};y(),s.addEventListener("change",y),l.appendChild(V("Which fields",s)),l.appendChild(b);break}case"step":{const s=le("fb-trig-p-step",e.step,"0");s.type="number",s.min="0",l.appendChild(V("Step",s,{hint:"Counting from 0"}));break}case"url":l.appendChild(V("Send them to",le("fb-trig-p-url",e.url,"/thank-you"),{full:!0}));break;case"theme":l.appendChild(V("Theme",J("fb-trig-p-theme",ht,e.theme||"auto")));break;case"intensity":l.appendChild(V("Intensity",J("fb-trig-p-intensity",[{value:"low",label:"Low"},{value:"medium",label:"Medium"},{value:"high",label:"High"}],e.intensity||"medium")));break;case"slug":{const s=[{value:"",label:fe.length?"Choose an action":"No actions available"}].concat(fe.map(d=>({value:d.slug,label:d.title||d.slug})));l.appendChild(V("Action",J("fb-trig-p-slug",s,e.slug||""),{full:!0}));break}default:break}n.kind==="event"&&!n.noOnce&&a.appendChild(pe("fb-trig-p-once",!!e.once,"Only once","Fire the first time only, rather than every time the answer swings back."));const r=a.querySelector(".fb-trig-p-finish-on");if(r){const c=()=>i.forEach(s=>{s.hidden=!r.checked});c(),r.addEventListener("change",c)}return l.children.length&&o.appendChild(l),a.children.length&&o.appendChild(a),o}function ln(e){return e?.kindText?e.kindText:e?.kind==="event"?"once, when it becomes true":"while it is true"}const an=(e,t=48)=>e.length>t?`${e.slice(0,t-1)}\u2026`:e,ae=(e,t)=>e.find(o=>o.value===t)?.label||t;function on(e){if(!e)return"";const t=[],o=l=>l?`"${an(l)}"`:"",n=()=>e.target==="listed"?e.fields?.length?e.fields.join(", "):"no fields chosen":ae(ft,e.target||"rest").toLowerCase();switch(e.type){case"banner":t.push(o(e.message)||"No message yet",ae(Ce,e.level||"info"),ae(mt,e.position||"field-below").toLowerCase());break;case"toast":t.push(o(e.message)||"No message yet",ae(Ce,e.level||"info"));break;case"hide-fields":case"disable-fields":case"require-fields":t.push(n());break;case"block-submit":t.push(o(e.message)||"the standard reason");break;case"end-form":t.push(o(e.message)||"no message"),e.finish===!1?t.push("submit disabled"):t.push(`button "${e.finishLabel||"Finish"}"`,e.record===!1?"saves nothing":"saves the answers");break;case"goto-step":t.push(Number.isFinite(e.step)?`step ${e.step}`:"no step yet");break;case"redirect":t.push(e.url||"no address yet");break;case"celebrate":t.push(ae(ht,e.theme||"auto"),`${e.intensity||"medium"} intensity`);break;case"run-action":t.push(e.slug?fe.find(l=>l.slug===e.slug)?.title||e.slug:"no action chosen");break;case"notify":t.push(o(e.message)||"titled after the form",ae(Ce,e.level||"info"));break;default:break}return e.once&&t.push("only once"),t.filter(Boolean).join(" \xB7 ")}function De(e,t,{open:o=!1}={}){const n=document.createElement("div");n.className="fb-trig-action-row";const l=document.createElement("div");l.className="fb-trig-action-head",l.tabIndex=0,l.setAttribute("role","button");const a=document.createElement("span");a.className="fb-trig-chevron";const i=document.createElement("span");i.className="fb-trig-action-main";const r=document.createElement("strong"),c=document.createElement("small");c.className="fb-trig-action-summary",i.append(r,c);const s=document.createElement("span");s.className="fb-trig-kind",l.append(a,i,s,ze(()=>n.remove(),"Remove action"));const d=document.createElement("div");d.className="fb-trig-action-body";const u=J("fb-trig-action-type",de,e.type||"banner");d.appendChild(V("Action",u,{full:!0})),d.appendChild(yt(e,t));const b=()=>{const m=de.find(f=>f.value===u.value);r.textContent=m?.label||u.value,s.textContent=ln(m),s.classList.toggle("fb-trig-kind--event",m?.kind==="event"),c.textContent=on(Et(n))},y=m=>{d.hidden=!m,n.classList.toggle("is-open",m),l.setAttribute("aria-expanded",String(m)),q(a,m?"chevron-down":"chevron-right",12)};return l.addEventListener("click",()=>y(d.hidden)),l.addEventListener("keydown",m=>{m.target!==l||m.key!=="Enter"&&m.key!==" "||(m.preventDefault(),y(d.hidden))}),u.addEventListener("change",()=>{const m=yt({type:u.value},t);d.replaceChild(m,d.querySelector(".fb-trig-params")),b(),window.I?.scan?.()}),d.addEventListener("input",b),d.addEventListener("change",b),n.append(l,d),y(o),b(),n}function vt(e,t,o){const n=document.createElement("section");n.className=`fb-trig-sec fb-trig-sec--${e}`;const l=document.createElement("div");l.className="fb-trig-sec-head";const a=document.createElement("span");if(a.className="fb-trig-sec-title",a.textContent=t,l.appendChild(a),o){const i=document.createElement("span");i.className="fb-trig-sec-hint",i.textContent=o,l.appendChild(i)}return n.appendChild(l),n}function Pe(e,t){const o=document.createElement("button");return o.type="button",o.className="btn btn-xs btn-ghost fb-trig-add",re(o,"plus",e,12),o.addEventListener("click",t),o}function xt(e,t,o,n){const a=vt(e,t,e==="then"?"What happens while the answers match.":"What happens while they do not. Leave empty for nothing.");a.classList.add("fb-trig-branch"),a.dataset.branch=e;const i=document.createElement("div");return i.className="fb-trig-action-list",(o||[]).forEach(r=>i.appendChild(De(r,n))),a.appendChild(i),a.appendChild(Pe("Add action",()=>{i.appendChild(De({type:"banner"},n,{open:!0})),window.I?.scan?.()})),a}function Ct(e,t,o){const n=bt(t,!0),l=document.createElement("div");l.className="fb-trig-card",l.dataset.triggerId=e.id||`trg-${Date.now()}-${Math.random().toString(36).slice(2,7)}`;const a=document.createElement("div");a.className="fb-trig-card-head";const i=document.createElement("span");i.className="fb-trig-card-title",i.textContent=`Trigger ${o+1}`,a.appendChild(i);const r=pe("fb-trig-enabled",e.enabled!==!1,"Enabled","Untick to keep the trigger but switch it off.");if(a.appendChild(r),a.appendChild(ze(()=>l.remove(),"Remove trigger")),l.appendChild(a),!n.length){const u=document.createElement("p");return u.className="fb-trig-empty",u.textContent="Give this field a name first - a trigger tests an answer, and it needs something to test.",l.appendChild(u),l}const c=vt("when","When");c.querySelector(".fb-trig-sec-head").appendChild(J("fb-trig-match",[{value:"all",label:"all of these are true"},{value:"any",label:"any of these is true"}],Array.isArray(e.when?.any)?"any":"all"));const s=document.createElement("div");s.className="fb-trig-cond-list";const d=e.when?.all||e.when?.any||[];return(d.length?d:[{}]).forEach(u=>s.appendChild(gt(u,n))),c.appendChild(s),c.appendChild(Pe("Add condition",()=>{s.appendChild(gt({},n)),window.I?.scan?.()})),l.appendChild(c),l.appendChild(xt("then","Then",e.then,t)),l.appendChild(xt("otherwise","Otherwise",e.otherwise,t)),l}function sn(e,t){const o=Array.isArray(e.triggers)?e.triggers:[],n=document.createElement("div");n.className="fb-field-triggers";const l=document.createElement("div");l.className="fb-trig-panel-head";const a=document.createElement("span");a.className="fb-trig-panel-title",a.textContent=`\u{1F3AF} Triggers${o.length?` (${o.length})`:""}`;const i=document.createElement("button");i.type="button",i.className="btn btn-xs btn-ghost",q(i,o.length?"chevron-down":"chevron-right",12);const r=document.createElement("div");r.className="fb-triggers-body",r.hidden=!o.length,l.addEventListener("click",()=>{r.hidden=!r.hidden,q(i,r.hidden?"chevron-right":"chevron-down",12)});const c=document.createElement("p");c.className="fb-trig-blurb",c.textContent="Do something to the form when the answers meet a condition - show a banner or toast, hide, disable or require other fields, refuse the submit, end the form early, notify the admins, jump to a step, or celebrate.",r.appendChild(c);const s=document.createElement("div");return s.className="fb-trig-list",o.forEach((d,u)=>s.appendChild(Ct(d,t,u))),r.appendChild(s),r.appendChild(Pe("Add trigger",()=>{const d=Ct({enabled:!0,then:[]},t,s.children.length);s.appendChild(d),d.querySelector('.fb-trig-branch[data-branch="then"] .fb-trig-action-list')?.appendChild(De({type:"banner"},t,{open:!0})),window.I?.scan?.()})),l.appendChild(a),l.appendChild(i),n.appendChild(l),n.appendChild(r),n}function Et(e){const t=e.querySelector(".fb-trig-action-type")?.value,o=de.find(a=>a.value===t);if(!o)return null;const n={type:t},l=a=>e.querySelector(a)?.value?.trim()||"";for(const a of o.params)switch(a){case"message":n.message=l(".fb-trig-p-message");break;case"finishLabel":{const i=l(".fb-trig-p-finish");i&&(n.finishLabel=i);break}case"doneMessage":{const i=l(".fb-trig-p-done");i&&(n.doneMessage=i);break}case"finish":e.querySelector(".fb-trig-p-finish-on")?.checked||(n.finish=!1);break;case"record":e.querySelector(".fb-trig-p-record")?.checked||(n.record=!1);break;case"level":n.level=l(".fb-trig-p-level")||"info";break;case"position":n.position=l(".fb-trig-p-position")||"field-below";break;case"dismissible":n.dismissible=!!e.querySelector(".fb-trig-p-dismissible")?.checked;break;case"target":{const i=l(".fb-trig-p-target")||"rest";n.target=i,i==="listed"&&(n.fields=Array.from(e.querySelectorAll(".fb-trig-p-fields option")).filter(r=>r.selected).map(r=>r.value));break}case"step":{const i=parseInt(l(".fb-trig-p-step"),10);Number.isFinite(i)&&(n.step=i);break}case"url":n.url=l(".fb-trig-p-url");break;case"theme":n.theme=l(".fb-trig-p-theme")||"auto";break;case"intensity":n.intensity=l(".fb-trig-p-intensity")||"medium";break;case"slug":n.slug=l(".fb-trig-p-slug");break;default:break}return o.kind==="event"&&!o.noOnce&&e.querySelector(".fb-trig-p-once")?.checked&&(n.once=!0),n}function rn(e){const t=Et(e);if(!t)return null;const o=de.find(a=>a.value===t.type),n=t.type,l=["block-submit","end-form","notify"].includes(o.value);return o.params.includes("message")&&!t.message&&!l?(delete t.message,null):(l&&!t.message&&delete t.message,n==="redirect"&&!t.url||n==="run-action"&&!t.slug?null:t)}function cn(e){const o=document.querySelector(`.fb-field-card[data-index="${e}"]`)?.querySelector(".fb-field-triggers");if(!o)return;const n=[];return o.querySelectorAll(".fb-trig-card").forEach(l=>{const a=Array.from(l.querySelectorAll(".fb-trig-cond-row")).map(u=>{const b=u.querySelector(".fb-trig-cond-field")?.value,y=u.querySelector(".fb-trig-cond-op")?.value;if(!b||!y)return null;const m=u.querySelector(".fb-trig-cond-val")?.value??"";return{field:b,operator:y,value:m}}).filter(Boolean);if(!a.length)return;const i=u=>Array.from(l.querySelectorAll(`.fb-trig-branch[data-branch="${u}"] .fb-trig-action-row`)).map(rn).filter(Boolean),r=i("then"),c=i("otherwise");if(!r.length&&!c.length)return;const s=l.querySelector(".fb-trig-match")?.value==="any"?"any":"all",d={id:l.dataset.triggerId,enabled:!!l.querySelector(".fb-trig-enabled")?.checked,when:{[s]:a}};r.length&&(d.then=r),c.length&&(d.otherwise=c),n.push(d)}),n.length?n:void 0}function wt(e,t,o){const n=document.createElement("div");return n.className="fb-field-extras",e==="chooser"?(n.appendChild(dn(t,o)),n.appendChild(un(t.options||[],o))):(Fe.has(e)&&n.appendChild(gn(t.options||[],o)),e==="textarea"&&n.appendChild(D([P("Rows",`fb-rows-${o}`,"number",t.formConfig?.rows||4,"Height of textarea")])),(e==="string"||e==="textarea")&&n.appendChild(D([P("Min Length",`fb-minlength-${o}`,"number",t.minLength||"",""),P("Max Length",`fb-maxlength-${o}`,"number",t.maxLength||"","")])),e==="number"&&n.appendChild(D([P("Min",`fb-min-${o}`,"number",t.min??"",""),P("Max",`fb-max-${o}`,"number",t.max??"","")]))),n.children.length?n:null}function kt(e){const t=document.createElement("div");t.className="fb-chooser-section",t.style.cssText="border:1px solid var(--dm-border); border-radius:6px; margin-top:.5rem; overflow:hidden;";const o=document.createElement("div");o.style.cssText="padding:.4rem .6rem; background:color-mix(in srgb, var(--dm-background) 60%, transparent); font-weight:600; font-size:.82rem; border-bottom:1px solid var(--dm-border);",o.textContent=e,t.appendChild(o);const n=document.createElement("div");return n.className="fb-chooser-section-body",n.style.cssText="padding:.55rem .6rem; display:flex; flex-direction:column; gap:.45rem;",t.appendChild(n),{wrap:t,body:n}}function Ee(e,t,o,n,l){const a=document.createElement("div");a.style.flex="1";const i=document.createElement("label");i.htmlFor=e,i.className="form-label",i.textContent=t,i.style.fontSize=".8rem";const r=document.createElement("select");if(r.id=e,r.className="form-input",n.forEach(([c,s])=>{const d=document.createElement("option");d.value=c,d.textContent=s,String(c)===String(o)&&(d.selected=!0),r.appendChild(d)}),a.appendChild(i),a.appendChild(r),l){const c=document.createElement("p");c.className="form-hint text-muted",c.textContent=l,c.style.cssText="font-size:.72rem;margin-top:.2rem;",a.appendChild(c)}return a}function St(e,t,o,n){const l=document.createElement("div");l.style.flex="1";const a=document.createElement("label");a.style.cssText="display:inline-flex;align-items:center;gap:.4rem;font-size:.82rem;";const i=document.createElement("input");if(i.id=e,i.type="checkbox",i.checked=!!o,a.appendChild(i),a.appendChild(document.createTextNode(" "+t)),l.appendChild(a),n){const r=document.createElement("p");r.className="form-hint text-muted",r.textContent=n,r.style.cssText="font-size:.72rem;margin-top:.2rem;",l.appendChild(r)}return l}function _e(e,t,o,n,l){const a=document.createElement("div");a.style.flex="1";const i=document.createElement("label");i.htmlFor=e,i.className="form-label",i.textContent=t,i.style.fontSize=".8rem",a.appendChild(i);const r=document.createElement("div");r.style.cssText="display:flex;gap:.4rem;align-items:stretch;";const c=document.createElement("input");c.id=e,c.type="text",c.className="form-input",c.value=o||"",n&&(c.placeholder=n),c.style.flex="1";const s=document.createElement("input");s.type="color",s.title="Pick a colour",s.style.cssText="width:36px;height:auto;padding:0;border:1px solid var(--dm-border);border-radius:4px;cursor:pointer;";const d=String(c.value).match(/^#[0-9a-fA-F]{3,8}$/);if(s.value=d?c.value:"#000000",s.addEventListener("input",()=>{c.value=s.value,c.dispatchEvent(new Event("input",{bubbles:!0}))}),r.appendChild(c),r.appendChild(s),a.appendChild(r),l){const u=document.createElement("p");u.className="form-hint text-muted",u.textContent=l,u.style.cssText="font-size:.72rem;margin-top:.2rem;",a.appendChild(u)}return a}function dn(e,t){const o=document.createElement("div");o.style.cssText="margin-top:.5rem; display:flex; flex-direction:column; gap:.4rem;";const n=kt("Layout");n.body.appendChild(D([Ee(`fb-chooser-variant-${t}`,"Variant",e.variant||"card",[["card","Card - rich tiles"],["chip","Chip - compact pills"]],"Card supports description and grid columns; chips wrap."),St(`fb-chooser-multiple-${t}`,"Multi-select",!!e.multiple,"Off = single (radio); on = multi (checkbox).")])),n.body.appendChild(D([Ee(`fb-chooser-density-${t}`,"Density",e.density||"comfortable",[["comfortable","Comfortable"],["compact","Compact"]],"Compact strips description (card variant only)."),P("Columns",`fb-chooser-columns-${t}`,"number",e.columns||3,"Card grid (1-6). Ignored for chips.")])),o.appendChild(n.wrap);const l=kt("Visual");return l.body.appendChild(D([_e(`fb-chooser-accent-${t}`,"Accent",e.accent||"","primary / success / #ec4899","Selected highlight colour - semantic name or any CSS colour."),Ee(`fb-chooser-accent-style-${t}`,"Accent style",e.accentStyle||"border",[["border","Border (default)"],["solid","Solid"],["glow","Glow"],["overlay","Overlay"],["underline","Underline"]],"Visual treatment of the selected state.")])),l.body.appendChild(D([St(`fb-chooser-glow-${t}`,"Glow on selected",!!e.glow,"Soft outer glow."),_e(`fb-chooser-glow-colour-${t}`,"Glow colour",e.glowColour||"","defaults to accent","Optional; semantic name or CSS colour.")])),l.body.appendChild(D([Ee(`fb-chooser-shadow-${t}`,"Shadow",e.shadow||"none",[["none","None"],["sm","Small"],["md","Medium"],["lg","Large"],["xl","Extra Large"]],"Drop shadow on every option."),_e(`fb-chooser-shadow-colour-${t}`,"Shadow colour",e.shadowColour||"","rgba(0,0,0,0.1)","Optional shadow tint.")])),o.appendChild(l.wrap),o}function pn(e,t,o,n){const l=document.createElement("div");l.className="fb-chooser-option-row",l.dataset.optIdx=String(o),l.style.cssText="border:1px solid var(--dm-border); border-radius:4px; padding:.5rem; background:var(--dm-background-alt); display:flex; flex-direction:column; gap:.35rem;";const a=document.createElement("div");a.style.cssText="display:flex; gap:.4rem; align-items:center;";const i=document.createElement("div");i.style.cssText="display:flex; flex-direction:column; gap:1px;";const r=document.createElement("button");r.type="button",r.className="btn btn-xs btn-ghost",r.title="Move up",q(r,"arrow-up"),r.addEventListener("click",I=>{I.preventDefault(),Tt(t,o,-1,n)});const c=document.createElement("button");c.type="button",c.className="btn btn-xs btn-ghost",c.title="Move down",q(c,"arrow-down"),c.addEventListener("click",I=>{I.preventDefault(),Tt(t,o,1,n)}),i.appendChild(r),i.appendChild(c),a.appendChild(i);const s=document.createElement("input");s.type="text",s.className="form-input fb-chooser-opt-value",s.placeholder="value",s.value=e.value||"",s.style.flex="1",a.appendChild(s);const d=document.createElement("input");d.type="text",d.className="form-input fb-chooser-opt-label",d.placeholder="label (visible to users)",d.value=e.label||"",d.style.flex="2",a.appendChild(d);const u=document.createElement("button");u.type="button",u.className="btn btn-xs btn-ghost",u.style.cssText="line-height:1;opacity:.6;",u.title="Remove option",q(u,"x"),u.addEventListener("click",I=>{I.preventDefault(),hn(t,o,n)}),a.appendChild(u),l.appendChild(a);const b=document.createElement("div");b.style.cssText="display:flex; gap:.4rem;";const y=Mt("icon name (optional)",e.icon||"");y.input.classList.add("fb-chooser-opt-icon"),y.el.style.flex="1",b.appendChild(y.el);const m=document.createElement("input");m.type="text",m.className="form-input fb-chooser-opt-description",m.placeholder="description (card + comfortable only)",m.value=e.description||"",m.style.flex="2",b.appendChild(m),l.appendChild(b);const f=document.createElement("div");f.style.cssText="display:flex; gap:.4rem;";const k=document.createElement("input");k.type="text",k.className="form-input fb-chooser-opt-tooltip",k.placeholder="tooltip (hover hint)",k.value=e.tooltip||"",k.style.flex="2",f.appendChild(k);const h=document.createElement("input");h.type="text",h.className="form-input fb-chooser-opt-badge-text",h.placeholder="badge text",h.value=e.badge?.text||"",h.style.flex="1",f.appendChild(h);const x=document.createElement("select");x.className="form-input fb-chooser-opt-badge-type",x.style.flex="1",[["","no badge"],["primary","primary"],["success","success"],["info","info"],["warning","warning"],["danger","danger"]].forEach(([I,X])=>{const H=document.createElement("option");H.value=I,H.textContent=X,(e.badge?.type||"")===I&&(H.selected=!0),x.appendChild(H)}),f.appendChild(x),l.appendChild(f);const $=document.createElement("div");$.style.cssText="display:flex; gap:1rem; font-size:.8rem;";const A=document.createElement("label");A.style.cssText="display:inline-flex;align-items:center;gap:.3rem;";const G=document.createElement("input");G.type="checkbox",G.className="fb-chooser-opt-recommended",G.checked=!!e.recommended,A.appendChild(G),A.appendChild(document.createTextNode(" Recommended")),$.appendChild(A);const _=document.createElement("label");_.style.cssText="display:inline-flex;align-items:center;gap:.3rem;";const j=document.createElement("input");return j.type="checkbox",j.className="fb-chooser-opt-disabled",j.checked=!!e.disabled,_.appendChild(j),_.appendChild(document.createTextNode(" Disabled")),$.appendChild(_),l.appendChild($),l}function un(e,t){const o=document.createElement("div");o.style.cssText="margin-top:.5rem;";const n=document.createElement("div");n.style.cssText="display:flex; align-items:center; justify-content:space-between; margin-bottom:.4rem;";const l=document.createElement("p");l.textContent="Options",l.style.cssText="font-size:.85rem;font-weight:600;margin:0;",n.appendChild(l);const a=document.createElement("button");a.type="button",a.className="btn btn-xs btn-ghost",re(a,"plus","Add option"),n.appendChild(a),o.appendChild(n);const i=document.createElement("div");i.className="fb-chooser-options-list",i.id=`fb-chooser-options-${t}`,i.style.cssText="display:flex; flex-direction:column; gap:.4rem;",o.appendChild(i);const r=()=>{i.replaceChildren();const c=mn(t);(c.length?c:e||[]).forEach((d,u)=>{i.appendChild(pn(d,t,u,r))})};return a.addEventListener("click",c=>{c.preventDefault(),fn(t,r)}),r(),o}function mn(e){const t=C[e];return!t||!Array.isArray(t.options)?[]:t.options}function je(e){return C[e]?(Array.isArray(C[e].options)||(C[e].options=[]),C[e].options):[]}function fn(e,t){we(e),je(e).push({value:"new",label:"New option"}),t()}function hn(e,t,o){we(e),je(e).splice(t,1),o()}function Tt(e,t,o,n){we(e);const l=je(e),a=t+o;if(a<0||a>=l.length)return;const[i]=l.splice(t,1);l.splice(a,0,i),n()}function we(e){const t=document.getElementById(`fb-chooser-options-${e}`);if(!t)return;const o=t.querySelectorAll(".fb-chooser-option-row"),n=[];o.forEach(l=>{const a={value:l.querySelector(".fb-chooser-opt-value")?.value.trim()||"",label:l.querySelector(".fb-chooser-opt-label")?.value.trim()||""},i=l.querySelector(".fb-chooser-opt-icon")?.value.trim();i&&(a.icon=i);const r=l.querySelector(".fb-chooser-opt-description")?.value.trim();r&&(a.description=r);const c=l.querySelector(".fb-chooser-opt-tooltip")?.value.trim();c&&(a.tooltip=c);const s=l.querySelector(".fb-chooser-opt-badge-text")?.value.trim(),d=l.querySelector(".fb-chooser-opt-badge-type")?.value;(s||d)&&(a.badge={},s&&(a.badge.text=s),d&&(a.badge.type=d)),l.querySelector(".fb-chooser-opt-recommended")?.checked&&(a.recommended=!0),l.querySelector(".fb-chooser-opt-disabled")?.checked&&(a.disabled=!0),n.push(a)}),C[e]&&(C[e].options=n)}function D(e){const t=document.createElement("div");return t.style.cssText="display:flex;gap:.85rem;margin-bottom:.75rem;",e.forEach(o=>{o&&t.appendChild(o)}),t}function P(e,t,o,n,l){const a=document.createElement("div");a.style.flex="1";const i=document.createElement("label");i.htmlFor=t,i.className="form-label",i.textContent=e,i.style.fontSize=".8rem";const r=document.createElement("input");if(r.id=t,r.type=o||"text",r.className="form-input",r.value=n??"",a.appendChild(i),a.appendChild(r),l){const c=document.createElement("p");c.className="form-hint text-muted",c.textContent=l,c.style.cssText="font-size:.73rem;margin-top:.35rem;",a.appendChild(c)}return a}function bn(e,t,o,n){const l=document.createElement("div");l.style.flex="1";const a=document.createElement("label");a.htmlFor=t,a.className="form-label",a.textContent=e,a.style.fontSize=".8rem";const i=document.createElement("select");return i.id=t,i.className="form-select",o.forEach(r=>{const c=document.createElement("option");c.value=r.value,c.textContent=r.label,r.value===n&&(c.selected=!0),i.appendChild(c)}),l.appendChild(a),l.appendChild(i),l}function Nt(e,t,o){const n=document.createElement("div");n.style.cssText="flex:0;min-width:80px;display:flex;flex-direction:column;justify-content:flex-end;";const l=document.createElement("label");l.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;font-size:.8rem;white-space:nowrap;";const a=document.createElement("input");return a.id=t,a.type="checkbox",a.checked=o,l.appendChild(a),l.appendChild(document.createTextNode(e)),n.appendChild(l),n}function gn(e,t){const o=document.createElement("div");o.style.cssText="margin-top:.4rem;";const n=document.createElement("p");n.textContent="Options (one per line: value or value:Label)",n.style.cssText="font-size:.8rem;font-weight:600;margin-bottom:.3rem;";const l=document.createElement("textarea");return l.id=`fb-options-${t}`,l.className="form-input",l.rows=4,l.placeholder=`yes:Yes
1
+ import{apiRequest as ie}from"/admin/js/api.js";import{makeIconInput as et}from"/admin/js/lib/shortcode-modal.js";import{enhancePickerFields as tt}from"/public/js/forms.js";import{buildWizardSteps as jt}from"/public/js/form-steps.mjs";import{addBannerAction as qe}from"../lib/plugin-chrome.js";import{helpPopovers as Vt}from"../lib/help-popover.js";import{openMenuAt as nt,sidebarCount as Gt}from"../lib/tool-kit.js";import{draftKey as se,draftOf as Ht,duplicateField as Wt,fieldTitle as $e,fingerprint as lt,leavingEditor as at,moveField as ot,restoreDraft as Ut,snapshotOf as Ae}from"../lib/form-editor-arrange.js";import{fmtDateTime as Jt}from"../lib/dates.js";const it=[{value:"string",label:"Text (single line)"},{value:"email",label:"Email"},{value:"tel",label:"Phone"},{value:"number",label:"Number"},{value:"textarea",label:"Textarea (multi-line)"},{value:"select",label:"Dropdown (select)"},{value:"radio",label:"Radio buttons"},{value:"checkbox",label:"Single checkbox"},{value:"checkbox-group",label:"Checkbox group"},{value:"chooser",label:"Chooser (visual options)"},{value:"icon",label:"Icon (icon picker)"},{value:"image",label:"Image (media library)"},{value:"date",label:"Date"},{value:"time",label:"Time"},{value:"url",label:"URL"},{value:"password",label:"Password"},{value:"file",label:"File upload"},{value:"hidden",label:"Hidden field"}],Fe=new Set(["select","radio","checkbox-group","chooser"]);let w=[],fe=[],q=null,he=null,ne=null,be={};function A(e,t,o=14){e.textContent="";const n=document.createElement("span");return n.setAttribute("data-icon",t),n.setAttribute("data-icon-size",String(o)),n.style.cssText="display:inline-flex;align-items:center;vertical-align:middle;",e.appendChild(n),window.I?.scan?.(e),e}function re(e,t,o,n=14){return A(e,t,n),e.appendChild(document.createTextNode(" "+o)),e}function Ie(e){const t=document.createElement("span");return t.setAttribute("data-icon","help-circle"),t.setAttribute("data-icon-size","12"),t.style.cssText="display:inline-flex;align-items:center;vertical-align:middle;margin-left:.3rem;opacity:.55;cursor:help;",t.title=e,window.I?.scan?.(t),requestAnimationFrame(()=>{try{t.parentNode&&typeof E<"u"&&E.tooltip&&(t.removeAttribute("title"),E.tooltip(t,{content:e,position:"top"}))}catch{}}),t}let ge=null,st=null,Be=null,ce=null;function Oe(){Be?.(),clearTimeout(st),st=setTimeout(()=>{ge&&ge()},350)}function rt(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_|_$/g,"")}function ct(e){return it.find(t=>t.value===e)?.label||e}function dt(e){return(e||[]).some(t=>t.logic||Array.isArray(t.triggers)&&t.triggers.length)}function pt(e){return e?!!(e.visibility?.conditions?.length||e.requirement?.conditions?.length||e.validation?.length||e.cascade?.sourceField):!1}function Kt(e){const t={...w[e]};if(t.type==="spacer")return t;if(t.type==="page-break"){const m=document.getElementById(`fb-pb-label-${e}`),x=document.getElementById(`fb-pb-desc-${e}`);return m&&(t.label=m.value.trim()||t.label),x&&(t.description=x.value.trim()),t}const o=document.getElementById(`fb-label-${e}`),n=document.getElementById(`fb-name-${e}`),l=document.getElementById(`fb-type-${e}`),a=document.getElementById(`fb-required-${e}`),i=document.getElementById(`fb-placeholder-${e}`),r=document.getElementById(`fb-helper-${e}`),c=document.getElementById(`fb-tooltip-${e}`);if(o&&(t.label=o.value.trim()||t.label),n&&(t.name=n.value.trim()||t.name),l&&(t.type=l.value||t.type),a&&(t.required=a.checked),i&&(t.placeholder=i.value.trim()),r&&(t.helper=r.value.trim()),c&&(t.tooltip=c.value.trim()),t.type==="chooser"){const m=document.getElementById(`fb-chooser-variant-${e}`)?.value;m&&(t.variant=m);const x=document.getElementById(`fb-chooser-multiple-${e}`);x&&(t.multiple=x.checked);const N=document.getElementById(`fb-chooser-density-${e}`)?.value;N&&(t.density=N);const T=document.getElementById(`fb-chooser-columns-${e}`)?.value;T!==""&&T!==void 0&&(t.columns=parseInt(T,10)||3);const I=document.getElementById(`fb-chooser-accent-${e}`)?.value.trim();I?t.accent=I:delete t.accent;const B=document.getElementById(`fb-chooser-accent-style-${e}`)?.value;B?t.accentStyle=B:delete t.accentStyle;const G=document.getElementById(`fb-chooser-glow-${e}`);G&&(t.glow=G.checked);const O=document.getElementById(`fb-chooser-glow-colour-${e}`)?.value.trim();O?t.glowColour=O:delete t.glowColour;const X=document.getElementById(`fb-chooser-shadow-${e}`)?.value;X&&X!=="none"?t.shadow=X:delete t.shadow;const W=document.getElementById(`fb-chooser-shadow-colour-${e}`)?.value.trim();W?t.shadowColour=W:delete t.shadowColour,we(e),w[e]&&Array.isArray(w[e].options)&&(t.options=w[e].options)}else if(Fe.has(t.type)){["variant","density","columns","accent","accentStyle","glow","glowColour","shadow","shadowColour"].forEach(x=>delete t[x]),t.type!=="checkbox-group"&&t.type!=="multiselect"&&delete t.multiple;const m=document.getElementById(`fb-options-${e}`);m&&(t.options=m.value.split(`
2
+ `).filter(x=>x.trim()).map(x=>{const[N,...T]=x.split(":");return{value:N.trim(),label:T.join(":").trim()||N.trim()}}))}for(const m of["prefix","suffix"]){const x=document.getElementById(`fb-${m}-icon-${e}`),N=document.getElementById(`fb-${m}-text-${e}`),T={...t.formConfig||{}};if(x||N){const I=x?.value.trim()||"",B=N?.value.trim()||"";I&&/^[a-z0-9-]+$/i.test(I)?T[m]={icon:I}:B?T[m]={text:B.slice(0,24)}:delete T[m]}else Lt.includes(t.type)||delete T[m];t.formConfig=Object.keys(T).length?T:void 0}if(t.type==="textarea"){const m=parseInt(document.getElementById(`fb-rows-${e}`)?.value,10);m>0&&(t.formConfig={...t.formConfig||{},rows:m})}const s=document.getElementById(`fb-span-${e}`),d=document.getElementById(`fb-fullwidth-${e}`);if(s||d){const m={...t.formConfig||{}};if(d?.checked)m.span="full";else{const x=parseInt(s?.value,10);x>1?m.span=x:delete m.span}t.formConfig=Object.keys(m).length?m:void 0}const u=document.getElementById(`fb-minlength-${e}`)?.value;u&&(t.minLength=parseInt(u,10));const b=document.getElementById(`fb-maxlength-${e}`)?.value;b&&(t.maxLength=parseInt(b,10));const y=document.getElementById(`fb-min-${e}`)?.value;y!==""&&y!==void 0&&(t.min=parseFloat(y));const f=document.getElementById(`fb-max-${e}`)?.value;f!==""&&f!==void 0&&(t.max=parseFloat(f));const h=Yt(e);h?t.logic=h:delete t.logic;const k=bn(e);return k?t.triggers=k:delete t.triggers,t}function Yt(e){const t=document.querySelector(`.fb-field-card[data-index="${e}"]`);if(!t)return;const o=t.querySelector(".fb-field-logic");if(!o)return;const n={};let l=!1;const a=o.querySelector('[data-logic-section="visibility"]');if(a){const s=a.querySelector(".fb-logic-vis-default"),d=a.querySelector(".fb-logic-vis-transition"),u=Array.from(a.querySelectorAll(".fb-logic-cond-row")).map(h=>{const k=h.querySelector(".fb-logic-cond-field"),m=h.querySelector(".fb-logic-cond-op"),x=h.querySelector(".fb-logic-cond-val"),N=h.querySelector(".fb-logic-vis-then");return k?.value?{when:{all:[{field:k.value,operator:m.value,value:x.value}]},then:N.value}:null}).filter(Boolean),b=s?.value||"visible",y=d?.value||"none",f=parseInt(a.querySelector(".fb-logic-vis-duration")?.value||"",10);(b!=="visible"||u.length>0||y!=="none")&&(n.visibility={default:b,conditions:u},y!=="none"&&(n.visibility.transition=y),y!=="none"&&Number.isFinite(f)&&(n.visibility.duration=f),l=!0)}const i=o.querySelector('[data-logic-section="requirement"]');if(i){const s=i.querySelector(".fb-logic-req-default"),d=Array.from(i.querySelectorAll(".fb-logic-cond-row")).map(u=>{const b=u.querySelector(".fb-logic-cond-field"),y=u.querySelector(".fb-logic-cond-op"),f=u.querySelector(".fb-logic-cond-val"),h=u.querySelector(".fb-logic-req-then");return b?.value?{when:{all:[{field:b.value,operator:y.value,value:f.value}]},then:h.value==="true"}:null}).filter(Boolean);d.length>0&&(n.requirement={default:s?.checked===!0,conditions:d},l=!0)}const r=o.querySelector('[data-logic-section="validation"]');if(r){const s=Array.from(r.querySelectorAll(".fb-logic-val-rule")).map(d=>{const u=d.querySelector(".fb-logic-val-type"),b=d.querySelector(".fb-logic-val-pattern"),y=d.querySelector(".fb-logic-val-flags"),f=d.querySelector(".fb-logic-val-message");if(!b?.value.trim())return null;const h=u?.value||"regex",k={type:h,message:f?.value.trim()||"Invalid value."};return h==="regex"?(k.pattern=b.value.trim(),y?.value.trim()&&(k.flags=y.value.trim())):k.field=b.value.trim(),k}).filter(Boolean);s.length>0&&(n.validation=s,l=!0)}const c=o.querySelector('[data-logic-section="cascade"]');if(c){const s=c.querySelector(".fb-logic-cascade-source"),d=c.querySelector(".fb-logic-cascade-mapping"),u=c.querySelector(".fb-logic-cascade-defaults"),b=s?.value?.trim();if(b){let y={};try{y=JSON.parse(d?.value||"{}")}catch{}const f=(u?.value||"").split(`
3
+ `).filter(h=>h.trim()).map(h=>{const[k,...m]=h.split(":");return{value:k.trim(),label:m.join(":").trim()||k.trim()}});n.cascade={sourceField:b,mapping:y,defaultOptions:f},l=!0}}return l?n:void 0}function D(){w=w.map((e,t)=>Kt(t))}function Qt(e){const t=e.firstElementChild;if(!t||t.querySelector(".fb-drag-grip"))return;const o=document.createElement("span");o.className="fb-drag-grip",o.setAttribute("data-icon","grip-vertical"),o.setAttribute("data-icon-size","16"),o.title="Drag to reorder",t.insertBefore(o,t.firstChild),window.I?.scan?.(t)}function Q(e){const t=e.find("#fields-list").get(0),o=e.find("#fields-empty-msg").get(0);if(t){if(Array.from(t.querySelectorAll(".fb-field-card")).forEach(n=>n.remove()),w.length===0){o&&(o.style.display=""),ce?.(t),Oe();return}o&&(o.style.display="none"),w.forEach((n,l)=>{const a=n.type==="page-break"?Xt(n,l,e):n.type==="spacer"?Zt(n,l,e):en(n,l,e);Qt(a),t.appendChild(a)}),ce?.(t),Oe()}}function Xt(e,t,o){const n=document.createElement("div");n.className="fb-field-card",n.dataset.index=t,n.style.cssText="border:2px dashed var(--dm-border);border-radius:8px;overflow:hidden;margin-bottom:.75rem;background:var(--dm-card-bg);";const l=document.createElement("div");l.style.cssText="display:flex;align-items:center;gap:.6rem;padding:.6rem .8rem;cursor:pointer;user-select:none;";const a=document.createElement("span");a.textContent="- Page Break -",a.style.cssText="font-size:.7rem;padding:.15rem .5rem;border-radius:999px;background:color-mix(in srgb, var(--dm-text-muted) 20%, transparent);color:var(--dm-text-muted);white-space:nowrap;flex-shrink:0;font-style:italic;";const i=document.createElement("span");i.textContent=e.label||"Untitled Step",i.style.cssText="flex:1;font-weight:600;font-size:.9rem;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dm-text-muted);";const r=document.createElement("div");r.style.cssText="display:flex;gap:.25rem;flex-shrink:0;margin-left:.5rem;";const c=document.createElement("button");c.className="btn btn-xs btn-ghost",c.title="Edit step",A(c,"more-horizontal"),c.addEventListener("click",y=>{y.stopPropagation(),d.style.display=d.style.display==="none"?"":"none"}),r.appendChild(c);const s=document.createElement("button");s.className="btn btn-xs btn-ghost",s.style.cssText="line-height:1;opacity:.6;",s.title="Remove page break",A(s,"x"),s.addEventListener("click",async y=>{y.stopPropagation(),await E.confirm("Remove this page break?")&&(D(),w.splice(t,1),Q(o))}),r.appendChild(s),l.appendChild(a),l.appendChild(i),l.appendChild(r),l.addEventListener("click",()=>{d.style.display=d.style.display==="none"?"":"none"});const d=document.createElement("div");d.className="fb-field-body",d.style.cssText="padding:.8rem;border-top:1px dashed var(--dm-border);display:none;";const u=_([V("Step Title",`fb-pb-label-${t}`,"text",e.label||"","Shown as the wizard step heading"),V("Step Description",`fb-pb-desc-${t}`,"text",e.description||"","Optional sub-heading")]),b=u.querySelector(`#fb-pb-label-${t}`);return b&&b.addEventListener("input",()=>{i.textContent=b.value||"Untitled Step"}),d.appendChild(u),n.appendChild(l),n.appendChild(d),n}function Zt(e,t,o){const n=document.createElement("div");n.className="fb-field-card",n.dataset.index=t,n.style.cssText="border:1px dashed var(--dm-border);border-radius:6px;margin-bottom:.5rem;background:transparent;";const l=document.createElement("div");l.style.cssText="display:flex;align-items:center;gap:.6rem;padding:.4rem .8rem;";const a=document.createElement("div");a.style.cssText="flex:1;height:1px;background:var(--dm-border);";const i=document.createElement("span");i.textContent="Spacer",i.style.cssText="font-size:.7rem;color:var(--dm-text-muted);white-space:nowrap;padding:0 .4rem;font-style:italic;";const r=document.createElement("div");r.style.cssText="flex:1;height:1px;background:var(--dm-border);";const c=document.createElement("div");c.style.cssText="display:flex;gap:.25rem;flex-shrink:0;";const s=document.createElement("button");return s.className="btn btn-xs btn-ghost",s.style.cssText="line-height:1;opacity:.6;",s.title="Remove spacer",A(s,"x"),s.addEventListener("click",async d=>{d.stopPropagation(),D(),w.splice(t,1),Q(o)}),c.appendChild(s),l.appendChild(a),l.appendChild(i),l.appendChild(r),l.appendChild(c),n.appendChild(l),n}function en(e,t,o){const n=document.createElement("div");n.className="fb-field-card",n.dataset.index=t,n.style.cssText="border:1px solid var(--dm-border);border-radius:8px;overflow:hidden;margin-bottom:.75rem;background:var(--dm-card-bg);box-shadow:var(--dm-shadow-sm);";const l=document.createElement("div");l.style.cssText="display:flex;align-items:center;gap:.6rem;padding:.65rem .85rem;background:var(--dm-card-header-bg);cursor:pointer;user-select:none;";const a=document.createElement("span");a.textContent=ct(e.type),a.style.cssText="font-size:.7rem;padding:.15rem .45rem;border-radius:999px;background:color-mix(in srgb, var(--dm-primary) 14%, transparent);color:var(--dm-primary);white-space:nowrap;flex-shrink:0;";const i=document.createElement("span");i.textContent=e.label||"(unlabelled)",i.style.cssText="flex:1;font-weight:600;font-size:.9rem;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;";const r=document.createElement("div");r.style.cssText="display:flex;gap:.25rem;flex-shrink:0;margin-left:.5rem;";const c=document.createElement("button");c.className="btn btn-xs btn-ghost",c.title="Edit field",A(c,"more-horizontal"),c.addEventListener("click",u=>{u.stopPropagation(),d.style.display=d.style.display==="none"?"":"none"}),r.appendChild(c);const s=document.createElement("button");if(s.className="btn btn-xs btn-ghost",s.style.cssText="line-height:1;opacity:.6;",s.title="Remove field",A(s,"x"),s.addEventListener("click",async u=>{u.stopPropagation(),await E.confirm("Remove this field?")&&(D(),w.splice(t,1),Q(o))}),r.appendChild(s),l.appendChild(a),l.appendChild(i),e.required){const u=document.createElement("span");u.textContent="required",u.style.cssText="font-size:.7rem;color:var(--dm-danger);flex-shrink:0;",l.appendChild(u)}if(pt(e.logic)){const u=document.createElement("span");A(u,"zap"),u.title="Has conditional logic",u.style.cssText="font-size:.75rem;color:var(--dm-primary);flex-shrink:0;",l.appendChild(u)}l.appendChild(r),l.addEventListener("click",()=>{d.style.display=d.style.display==="none"?"":"none"});const d=tn(e,t,i);return d.style.display="none",n.appendChild(l),n.appendChild(d),n}function tn(e,t,o){const n=document.createElement("div");n.className="fb-field-body",n.style.cssText="padding:1rem .9rem;border-top:1px solid var(--dm-border);";const l=_([V("Label",`fb-label-${t}`,"text",e.label||"","Shown above the field"),V("Field Name",`fb-name-${t}`,"text",e.name||"","Used as data key")]),a=_([Sn("Type",`fb-type-${t}`,it,e.type||"string"),Ft("Required",`fb-required-${t}`,e.required||!1)]),i=_([V("Placeholder",`fb-placeholder-${t}`,"text",e.placeholder||"","Hint text inside the field")]),r=_([V("Helper Text",`fb-helper-${t}`,"text",e.helper||"","Shown below the field"),V("Tooltip",`fb-tooltip-${t}`,"text",e.tooltip||"","Shown on hover via a help icon next to the label")]),c=e.formConfig?.span,s=_([V("Column Span",`fb-span-${t}`,"number",c&&c!=="full"?String(c):"1","Columns to span (grid only)"),Ft("Full Width",`fb-fullwidth-${t}`,c==="full")]);s.classList.add("fb-grid-row"),s.style.display=document.getElementById("setting-layout")?.value==="grid"?"flex":"none",n.appendChild(l),n.appendChild(a),n.appendChild(i),n.appendChild(r),n.appendChild(s);const d=n.querySelector(`#fb-label-${t}`),u=n.querySelector(`#fb-name-${t}`);d&&d.addEventListener("input",()=>{o&&(o.textContent=d.value||"(unlabelled)"),u&&!u.dataset.manuallyEdited&&(u.value=rt(d.value))}),u&&u.addEventListener("input",()=>{u.dataset.manuallyEdited="1"});const b=n.querySelector(`#fb-type-${t}`);b&&b.addEventListener("change",()=>{const f=n.closest(".fb-field-card");if(f){const m=f.querySelector("span");m&&(m.textContent=ct(b.value))}const h=n.querySelector(".fb-field-extras");h&&h.remove();const k=Nt(b.value,e,t);k&&n.appendChild(k)});const y=Nt(e.type,e,t);return y&&n.appendChild(y),n.appendChild(sn(e,t)),n.appendChild(fn(e,t)),n}const ye=[{value:"equals",label:"equals"},{value:"not_equals",label:"does not equal"},{value:"contains",label:"contains"},{value:"not_contains",label:"does not contain"},{value:"starts_with",label:"starts with"},{value:"ends_with",label:"ends with"},{value:"greater_than",label:"is greater than"},{value:"less_than",label:"is less than"},{value:"is_empty",label:"is empty"},{value:"is_not_empty",label:"is not empty"},{value:"in",label:"is one of (comma sep)"},{value:"not_in",label:"is not one of (comma sep)"},{value:"matches_regex",label:"matches regex"}],Re=new Set(["is_empty","is_not_empty"]),ut={equals:"includes",not_equals:"does not include",contains:"any value contains",not_contains:"no value contains",starts_with:"any value starts with",ends_with:"any value ends with",greater_than:"any value is greater than",less_than:"any value is less than",is_empty:"nothing selected",is_not_empty:"something selected",in:"includes one of (comma sep)",not_in:"includes none of (comma sep)",matches_regex:"any value matches regex"};function Me(e){return!!e&&(e.type==="checkbox-group"||!!e.multiple)}function ve(e){const t=document.createElement("p");return t.textContent=e,t.style.cssText="font-size:.75rem;font-weight:700;color:var(--dm-text-muted);margin:0 0 .7rem;text-transform:uppercase;letter-spacing:.06em;",t}function xe(e,t,o,n,l){const a=document.createElement("div");a.className="fb-logic-cond-row",a.style.cssText="display:flex;gap:.55rem;align-items:center;margin-bottom:.6rem;flex-wrap:wrap;";const i=document.createElement("span");i.textContent="When",i.style.cssText="font-size:.73rem;color:var(--dm-text-muted);flex-shrink:0;";const r=document.createElement("select");r.className="form-select fb-logic-cond-field",r.style.cssText="flex:1 1 140px;min-width:140px;",t.forEach(f=>{const h=document.createElement("option");h.value=f.value,h.textContent=f.label,e&&f.value===e.field&&(h.selected=!0),r.appendChild(h)});const c=document.createElement("select");c.className="form-select fb-logic-cond-op",c.style.cssText="flex:1 1 160px;min-width:140px;",ye.forEach(f=>{const h=document.createElement("option");h.value=f.value,h.textContent=f.label,e&&f.value===e.operator&&(h.selected=!0),c.appendChild(h)});const s=()=>{const f=t.find(h=>h.value===r.value);Array.from(c.options).forEach(h=>{const k=ye.find(m=>m.value===h.value);h.textContent=f?.multi&&ut[h.value]||k?.label||h.value})};s(),r.addEventListener("change",s);const d=document.createElement("input");d.type="text",d.className="form-input fb-logic-cond-val",d.placeholder="value",d.style.cssText="flex:1 1 120px;min-width:100px;",d.value=e?.value||"",e&&Re.has(e.operator)&&(d.style.display="none"),c.addEventListener("change",()=>{d.style.display=Re.has(c.value)?"none":""});const u=document.createElement("span");A(u,"arrow-right"),u.style.cssText="font-size:.73rem;color:var(--dm-text-muted);flex-shrink:0;";const b=document.createElement("select");b.className=`form-select ${n}`,b.style.cssText="flex:1 1 140px;min-width:120px;",o.forEach(f=>{const h=document.createElement("option");h.value=f.value,h.textContent=f.label,f.value===l&&(h.selected=!0),b.appendChild(h)});const y=document.createElement("button");return y.type="button",y.className="btn btn-xs btn-ghost",y.style.cssText="line-height:1;opacity:.6;",A(y,"x"),y.style.flexShrink="0",y.addEventListener("click",()=>a.remove()),a.appendChild(i),a.appendChild(r),a.appendChild(c),a.appendChild(d),a.appendChild(u),a.appendChild(b),a.appendChild(y),a}function nn(e,t,o){const n=document.createElement("div");n.dataset.logicSection="visibility",n.appendChild(ve("Visibility"));const l=document.createElement("div");l.style.cssText="display:flex;align-items:center;gap:.6rem;margin-bottom:.65rem;flex-wrap:wrap;";const a=document.createElement("span");a.textContent="Default:",a.style.cssText="font-size:.8rem;flex-shrink:0;";const i=document.createElement("select");i.className="form-select fb-logic-vis-default",i.style.cssText="max-width:240px;",[{value:"visible",label:"Visible"},{value:"hidden",label:"Hidden"}].forEach(m=>{const x=document.createElement("option");x.value=m.value,x.textContent=m.label,m.value===(e.default||"visible")&&(x.selected=!0),i.appendChild(x)}),l.appendChild(a),l.appendChild(i),n.appendChild(l);const r=document.createElement("div");r.style.cssText="display:flex;align-items:center;gap:.6rem;margin-bottom:.65rem;flex-wrap:wrap;";const c=document.createElement("span");c.textContent="Transition:",c.style.cssText="font-size:.8rem;flex-shrink:0;";const s=document.createElement("select");s.className="form-select fb-logic-vis-transition",s.style.cssText="max-width:240px;",[{value:"none",label:"None (instant)"},{value:"fade",label:"Fade"},{value:"slide",label:"Slide"},{value:"scale",label:"Scale"}].forEach(m=>{const x=document.createElement("option");x.value=m.value,x.textContent=m.label,m.value===(e.transition||"none")&&(x.selected=!0),s.appendChild(x)}),r.appendChild(c),r.appendChild(s),n.appendChild(r);const d=document.createElement("div");d.style.cssText="display:flex;align-items:center;gap:.6rem;margin-bottom:.65rem;flex-wrap:wrap;";const u=document.createElement("span");u.textContent="Speed:",u.style.cssText="font-size:.8rem;flex-shrink:0;";const b=document.createElement("select");b.className="form-select fb-logic-vis-duration",b.style.cssText="max-width:240px;",[{value:"",label:"Default"},{value:"120",label:"Very fast (120ms)"},{value:"200",label:"Fast (200ms)"},{value:"350",label:"Medium (350ms)"},{value:"600",label:"Slow (600ms)"},{value:"1000",label:"Very slow (1s)"}].forEach(m=>{const x=document.createElement("option");x.value=m.value,x.textContent=m.label,m.value===String(e.duration??"")&&(x.selected=!0),b.appendChild(x)}),d.appendChild(u),d.appendChild(b),n.appendChild(d);const y=()=>{d.style.display=s.value==="none"?"none":""};y(),s.addEventListener("change",y);const f=document.createElement("div");f.className="fb-logic-vis-rules";const h=o.map(m=>({value:m.name,label:m.label||m.name,multi:Me(m)})),k=[{value:"visible",label:"Show"},{value:"hidden",label:"Hide"}];if((e.conditions||[]).forEach(m=>{const x=(m.when?.all||m.when?.any||[])[0],N=m.then==="hidden"?"hidden":"visible";h.length>0&&f.appendChild(xe(x,h,k,"fb-logic-vis-then",N))}),n.appendChild(f),h.length>0){const m=document.createElement("button");m.type="button",m.className="btn btn-xs btn-ghost",m.style.cssText="font-size:.73rem;margin-top:.35rem;",re(m,"plus","Add visibility rule"),m.addEventListener("click",()=>f.appendChild(xe(null,h,k,"fb-logic-vis-then","visible"))),n.appendChild(m)}return n}function ln(e,t,o){const n=document.createElement("div");n.dataset.logicSection="requirement",n.appendChild(ve("Conditional Requirement"));const l=document.createElement("label");l.style.cssText="display:flex;align-items:center;gap:.5rem;font-size:.8rem;cursor:pointer;margin-bottom:.65rem;";const a=document.createElement("input");a.type="checkbox",a.className="fb-logic-req-default",a.checked=e.default===!0,l.appendChild(a),l.appendChild(document.createTextNode("Required by default")),n.appendChild(l);const i=document.createElement("div");i.className="fb-logic-req-rules";const r=o.map(s=>({value:s.name,label:s.label||s.name,multi:Me(s)})),c=[{value:"true",label:"Make required"},{value:"false",label:"Make optional"}];if((e.conditions||[]).forEach(s=>{const d=(s.when?.all||s.when?.any||[])[0],u=s.then===!0?"true":"false";r.length>0&&i.appendChild(xe(d,r,c,"fb-logic-req-then",u))}),n.appendChild(i),r.length>0){const s=document.createElement("button");s.type="button",s.className="btn btn-xs btn-ghost",s.style.cssText="font-size:.73rem;margin-top:.35rem;",re(s,"plus","Add requirement rule"),s.addEventListener("click",()=>i.appendChild(xe(null,r,c,"fb-logic-req-then","true"))),n.appendChild(s)}return n}function mt(e){const t=document.createElement("div");t.className="fb-logic-val-rule",t.style.cssText="display:flex;gap:.55rem;align-items:center;margin-bottom:.6rem;flex-wrap:wrap;";const o=document.createElement("select");o.className="form-select fb-logic-val-type",o.style.cssText="flex:0 0 140px;",[{value:"regex",label:"Regex"},{value:"match",label:"Match field"}].forEach(r=>{const c=document.createElement("option");c.value=r.value,c.textContent=r.label,r.value===(e?.type||"regex")&&(c.selected=!0),o.appendChild(c)});const n=document.createElement("input");n.type="text",n.className="form-input fb-logic-val-pattern",n.placeholder=e?.type==="match"?"field name":"pattern",n.value=e?.pattern||e?.field||"",n.style.cssText="flex:3;";const l=document.createElement("input");l.type="text",l.className="form-input fb-logic-val-flags",l.placeholder="flags",l.value=e?.flags||"",l.style.cssText="flex:0 0 55px;",e?.type==="match"&&(l.style.display="none"),o.addEventListener("change",()=>{l.style.display=o.value==="match"?"none":"",n.placeholder=o.value==="match"?"field name":"pattern"});const a=document.createElement("input");a.type="text",a.className="form-input fb-logic-val-message",a.placeholder="Error message",a.value=e?.message||"",a.style.cssText="flex:4;";const i=document.createElement("button");return i.type="button",i.className="btn btn-xs btn-ghost",i.style.cssText="line-height:1;opacity:.6;",A(i,"x"),i.style.flexShrink="0",i.addEventListener("click",()=>t.remove()),t.appendChild(o),t.appendChild(n),t.appendChild(l),t.appendChild(a),t.appendChild(i),t}function an(e,t,o){const n=document.createElement("div");n.dataset.logicSection="validation",n.appendChild(ve("Custom Validation"));const l=document.createElement("div");l.className="fb-logic-val-rules",(e||[]).forEach(i=>l.appendChild(mt(i))),n.appendChild(l);const a=document.createElement("button");return a.type="button",a.className="btn btn-xs btn-ghost",a.style.cssText="font-size:.73rem;margin-top:.35rem;",re(a,"plus","Add validation rule"),a.addEventListener("click",()=>l.appendChild(mt(null))),n.appendChild(a),n}function on(e,t,o){const n=document.createElement("div");n.dataset.logicSection="cascade",n.appendChild(ve("Cascade Options")),n.lastChild.appendChild(Ie("Make this field's options depend on another field. When the source field changes, the options here are replaced by the mapping entry for its value; if nothing matches, the default options below are shown."));const l=document.createElement("div");l.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem;";const a=document.createElement("span");a.textContent="Source field:",a.style.cssText="font-size:.8rem;flex-shrink:0;";const i=document.createElement("select");i.className="form-select fb-logic-cascade-source",i.style.cssText="flex:1;";const r=document.createElement("option");r.value="",r.textContent="- none -",i.appendChild(r),o.forEach(b=>{const y=document.createElement("option");y.value=b.name,y.textContent=b.label||b.name,b.name===e.sourceField&&(y.selected=!0),i.appendChild(y)}),l.appendChild(a),l.appendChild(i),n.appendChild(l);const c=document.createElement("p");c.textContent='Mapping JSON - {"value":[{"value":"...","label":"..."}]}',c.appendChild(Ie(`Each key is a value of the source field; its list becomes this field's options when that value is picked. Example: {"uk":[{"value":"london","label":"London"}]} shows London when the source is "uk".`)),c.style.cssText="font-size:.73rem;color:var(--dm-text-muted);margin:.3rem 0 .2rem;";const s=document.createElement("textarea");s.className="form-input fb-logic-cascade-mapping",s.rows=4,s.style.cssText="font-family:monospace;",s.placeholder='{"uk": [{"value": "london", "label": "London"}]}',s.value=e.mapping?JSON.stringify(e.mapping,null,2):"",n.appendChild(c),n.appendChild(s);const d=document.createElement("p");d.textContent="Default options (one per line: value:Label)",d.appendChild(Ie("Shown before the source field has a value, or when its value has no mapping entry.")),d.style.cssText="font-size:.73rem;color:var(--dm-text-muted);margin:.3rem 0 .2rem;";const u=document.createElement("textarea");return u.className="form-input fb-logic-cascade-defaults",u.rows=3,u.style.cssText="font-family:monospace;",u.placeholder=`option1:Option 1
4
+ option2:Option 2`,u.value=(e.defaultOptions||[]).map(b=>{const y=typeof b=="string"?b:b.value??"",f=typeof b=="string"?b:b.label??y;return y===f?y:`${y}:${f}`}).join(`
5
+ `),n.appendChild(d),n.appendChild(u),n}function sn(e,t){const o=e.logic||{},n=w.filter((u,b)=>b!==t&&u.type!=="page-break"&&u.type!=="spacer"),l=document.createElement("div");l.className="fb-field-logic",l.style.cssText="margin-top:.75rem;border-top:1px solid var(--dm-border);padding-top:.5rem;";const a=document.createElement("div");a.style.cssText="display:flex;align-items:center;justify-content:space-between;cursor:pointer;padding:.15rem 0;";const i=document.createElement("span");i.style.cssText="font-size:.8rem;font-weight:600;color:var(--dm-text-muted);",i.textContent="\u26A1 Conditional Logic";const r=pt(o),c=document.createElement("button");c.type="button",c.className="btn btn-xs btn-ghost",A(c,r?"chevron-down":"chevron-right",12);const s=document.createElement("div");s.className="fb-logic-body",s.style.cssText="padding:.25rem 0 .25rem;"+(r?"":"display:none;"),a.addEventListener("click",()=>{const u=s.style.display==="none";s.style.display=u?"":"none",A(c,u?"chevron-down":"chevron-right",12)}),a.appendChild(i),a.appendChild(c),l.appendChild(a),s.appendChild(nn(o.visibility||{},t,n)),s.appendChild(ln(o.requirement||{},t,n)),s.appendChild(an(o.validation||[],t,n));const d=document.getElementById(`fb-type-${t}`)?.value||e.type;return Fe.has(d)&&s.appendChild(on(o.cascade||{},t,n)),l.appendChild(s),l}const de=[{value:"banner",label:"Show a banner",kind:"state",params:["message","level","position","dismissible"],placeholder:"e.g. You must be 18 or over to continue."},{value:"toast",label:"Show a toast",kind:"event",params:["message","level"],placeholder:"e.g. Thanks - carry on."},{value:"hide-fields",label:"Hide fields",kind:"state",params:["target"]},{value:"disable-fields",label:"Disable fields",kind:"state",params:["target"]},{value:"require-fields",label:"Make fields required",kind:"state",params:["target"]},{value:"block-submit",label:"Block submission",kind:"state",params:["message"],messageLabel:"Reason, shown under the button",placeholder:'Blank: "This form cannot be submitted yet."'},{value:"end-form",label:"End the form here",kind:"state",params:["message","finish","finishLabel","doneMessage","record"],messageLabel:"Message shown in place of the rest",placeholder:"e.g. Sorry - you must be 18 or over to apply."},{value:"goto-step",label:"Jump to a wizard step",kind:"event",params:["step"]},{value:"redirect",label:"Redirect after submit",kind:"event",params:["url"]},{value:"celebrate",label:"Celebrate",kind:"event",params:["theme","intensity"]},{value:"run-action",label:"Run a CMS action",kind:"event",params:["slug"]},{value:"notify",label:"Notify the admins",kind:"event",params:["message","level"],messageLabel:"Notification title",placeholder:"Blank: titled after the form",noOnce:!0,kindText:"when the form is submitted"}],Ce=[{value:"info",label:"Info"},{value:"success",label:"Success"},{value:"warning",label:"Warning"},{value:"error",label:"Error"}],ft=[{value:"field-below",label:"Below this field"},{value:"field-above",label:"Above this field"},{value:"form-top",label:"Top of the form"}],ht=[{value:"rest",label:"The rest of the form"},{value:"all",label:"Every other field"},{value:"listed",label:"Chosen fields"}],bt=[{value:"auto",label:"Whatever is in season"},{value:"christmas",label:"Christmas"},{value:"halloween",label:"Halloween"},{value:"valentines",label:"Valentine's Day"},{value:"guy-fawkes",label:"Guy Fawkes Night"},{value:"st-patricks",label:"St Patrick's Day"},{value:"st-andrews",label:"St Andrew's Day"},{value:"st-davids",label:"St David's Day"},{value:"st-georges",label:"St George's Day"}];function gt(e,t=!1){const o=(i,r)=>({value:i.name,label:`${i.label||i.name}${r?" (this field)":""}`,multi:Me(i)}),n=i=>i&&i.type!=="page-break"&&i.type!=="spacer"&&i.name,l=w.filter((i,r)=>r!==e&&n(i)).map(i=>o(i,!1)),a=w[e];return t&&n(a)?[o(a,!0),...l]:l}function J(e,t,o){const n=document.createElement("select");return n.className=`form-select ${e}`,t.forEach(l=>{const a=document.createElement("option");a.value=l.value,a.textContent=l.label,l.value===o&&(a.selected=!0),n.appendChild(a)}),n}function le(e,t,o){const n=document.createElement("input");return n.type="text",n.className=`form-input ${e}`,n.value=t==null?"":String(t),o&&(n.placeholder=o),n}function ze(e,t){const o=document.createElement("button");return o.type="button",o.className="btn btn-xs btn-ghost fb-trig-remove",t&&(o.title=t,o.setAttribute("aria-label",t)),A(o,"x"),o.addEventListener("click",n=>{n.stopPropagation(),e()}),o}function j(e,t,{full:o=!1,hint:n=""}={}){const l=document.createElement("label");l.className=`fb-trig-f${o?" fb-trig-f--full":""}`;const a=document.createElement("span");if(a.className="fb-trig-f-label",a.textContent=e,l.appendChild(a),l.appendChild(t),n){const i=document.createElement("span");i.className="fb-trig-f-hint",i.textContent=n,l.appendChild(i)}return l}function pe(e,t,o,n){const l=document.createElement("label");l.className="fb-trig-check",n&&(l.title=n);const a=document.createElement("input");return a.type="checkbox",a.className=`form-check ${e}`,a.checked=!!t,l.appendChild(a),l.appendChild(document.createTextNode(o)),l}function yt(e,t){const o=document.createElement("div");o.className="fb-trig-cond-row";const n=J("fb-trig-cond-field",t,e?.field),l=J("fb-trig-cond-op",ye,e?.operator),a=le("fb-trig-cond-val",e?.value,"value"),i=()=>{const c=t.find(s=>s.value===n.value);Array.from(l.options).forEach(s=>{const d=ye.find(u=>u.value===s.value);s.textContent=c?.multi&&ut[s.value]||d?.label||s.value})},r=()=>{a.hidden=Re.has(l.value)};return i(),r(),n.addEventListener("change",i),l.addEventListener("change",r),o.appendChild(n),o.appendChild(l),o.appendChild(a),o.appendChild(ze(()=>o.remove(),"Remove condition")),o}const vt=["banner","toast","block-submit","end-form"],xt=["password","file","image","signature"],rn="Put an answer in with {{field_name}} - e.g. Hello {{name}}. Pick one from Insert field.";function cn(){try{D()}catch{}return w.filter(e=>e&&e.name&&e.type!=="page-break"&&e.type!=="spacer"&&!xt.includes(e.type))}function Ct(e,t,o){const n=le(e,t,o),l=document.createElement("span");l.className="fb-token-row";const a=document.createElement("select");a.className="form-input fb-token-pick",a.title="Insert an answer into the message",a.setAttribute("aria-label","Insert a field's answer into the message");const i=()=>{a.replaceChildren(new Option("Insert field",""));for(const r of cn())a.appendChild(new Option(`${r.label||r.name} ({{${r.name}}})`,r.name))};return i(),a.addEventListener("focus",i),a.addEventListener("mousedown",i),a.addEventListener("change",()=>{const r=a.value;if(a.value="",!r)return;const c=`{{${r}}}`,s=n.selectionStart??n.value.length,d=n.selectionEnd??s;n.value=n.value.slice(0,s)+c+n.value.slice(d),n.focus(),n.setSelectionRange(s+c.length,s+c.length),n.dispatchEvent(new Event("input",{bubbles:!0})),n.dispatchEvent(new Event("change",{bubbles:!0}))}),l.append(n,a),l}function dn(e){const t=window.FormLogicEngine;if(!t?.triggerTokens)return[];const o=new Map((e||[]).filter(l=>l&&l.name).map(l=>[l.name,l])),n=new Set;for(const l of e||[])for(const a of l?.triggers||[])for(const i of[...a.then||[],...a.otherwise||[]])if(vt.includes(i?.type))for(const r of[...t.triggerTokens(i.message),...t.triggerTokens(i.doneMessage)]){const c=o.get(r);(!c||xt.includes(c.type))&&n.add(r)}return[...n]}function Et(e,t){const o=document.createElement("div");o.className="fb-trig-params";const n=de.find(c=>c.value===(e.type||"banner"));if(!n)return o;const l=document.createElement("div");l.className="fb-trig-grid";const a=document.createElement("div");a.className="fb-trig-opts";const i=[];for(const c of n.params)switch(c){case"message":l.appendChild(vt.includes(n.value)?j(n.messageLabel||"Message",Ct("fb-trig-p-message",e.message,n.placeholder||""),{full:!0,hint:rn}):j(n.messageLabel||"Message",le("fb-trig-p-message",e.message,n.placeholder||""),{full:!0}));break;case"finish":a.appendChild(pe("fb-trig-p-finish-on",e.finish!==!1,"Let the visitor finish (send the answers given so far)",'Untick to stop the form dead: the submit button is disabled and nothing is sent, so nothing is saved and "Notify the admins" cannot fire.'));break;case"finishLabel":{const s=j("Button text",le("fb-trig-p-finish",e.finishLabel,"Finish"));i.push(s),l.appendChild(s);break}case"doneMessage":{const s=j("Closing message",Ct("fb-trig-p-done",e.doneMessage,"Blank: the form's own success message"));i.push(s),l.appendChild(s);break}case"record":{const s=pe("fb-trig-p-record",e.record!==!1,"Save the answers given so far",`Untick to keep nothing: no submission is stored and the form's email, webhook and actions do not run. A "Notify the admins" action still fires.`);i.push(s),a.appendChild(s);break}case"level":l.appendChild(j(n.value==="notify"?"Severity":"Level",J("fb-trig-p-level",Ce,e.level||"info")));break;case"position":l.appendChild(j("Where",J("fb-trig-p-position",ft,e.position||"field-below")));break;case"dismissible":a.appendChild(pe("fb-trig-p-dismissible",e.dismissible!==!1,"The visitor can dismiss it"));break;case"target":{const s=J("fb-trig-p-target",ht,e.target||(e.fields?.length?"listed":"rest")),d=document.createElement("select");d.className="form-select fb-trig-p-fields",d.multiple=!0,d.size=4;const u=new Set(e.fields||[]);gt(t).forEach(f=>{const h=document.createElement("option");h.value=f.value,h.textContent=f.label,u.has(f.value)&&(h.selected=!0),d.appendChild(h)});const b=j("Fields",d,{hint:"Ctrl/Cmd-click to pick several"}),y=()=>{b.hidden=s.value!=="listed"};y(),s.addEventListener("change",y),l.appendChild(j("Which fields",s)),l.appendChild(b);break}case"step":{const s=le("fb-trig-p-step",e.step,"0");s.type="number",s.min="0",l.appendChild(j("Step",s,{hint:"Counting from 0"}));break}case"url":l.appendChild(j("Send them to",le("fb-trig-p-url",e.url,"/thank-you"),{full:!0}));break;case"theme":l.appendChild(j("Theme",J("fb-trig-p-theme",bt,e.theme||"auto")));break;case"intensity":l.appendChild(j("Intensity",J("fb-trig-p-intensity",[{value:"low",label:"Low"},{value:"medium",label:"Medium"},{value:"high",label:"High"}],e.intensity||"medium")));break;case"slug":{const s=[{value:"",label:fe.length?"Choose an action":"No actions available"}].concat(fe.map(d=>({value:d.slug,label:d.title||d.slug})));l.appendChild(j("Action",J("fb-trig-p-slug",s,e.slug||""),{full:!0}));break}default:break}n.kind==="event"&&!n.noOnce&&a.appendChild(pe("fb-trig-p-once",!!e.once,"Only once","Fire the first time only, rather than every time the answer swings back."));const r=a.querySelector(".fb-trig-p-finish-on");if(r){const c=()=>i.forEach(s=>{s.hidden=!r.checked});c(),r.addEventListener("change",c)}return l.children.length&&o.appendChild(l),a.children.length&&o.appendChild(a),o}function pn(e){return e?.kindText?e.kindText:e?.kind==="event"?"once, when it becomes true":"while it is true"}const un=(e,t=48)=>e.length>t?`${e.slice(0,t-1)}\u2026`:e,ae=(e,t)=>e.find(o=>o.value===t)?.label||t;function mn(e){if(!e)return"";const t=[],o=l=>l?`"${un(l)}"`:"",n=()=>e.target==="listed"?e.fields?.length?e.fields.join(", "):"no fields chosen":ae(ht,e.target||"rest").toLowerCase();switch(e.type){case"banner":t.push(o(e.message)||"No message yet",ae(Ce,e.level||"info"),ae(ft,e.position||"field-below").toLowerCase());break;case"toast":t.push(o(e.message)||"No message yet",ae(Ce,e.level||"info"));break;case"hide-fields":case"disable-fields":case"require-fields":t.push(n());break;case"block-submit":t.push(o(e.message)||"the standard reason");break;case"end-form":t.push(o(e.message)||"no message"),e.finish===!1?t.push("submit disabled"):t.push(`button "${e.finishLabel||"Finish"}"`,e.record===!1?"saves nothing":"saves the answers");break;case"goto-step":t.push(Number.isFinite(e.step)?`step ${e.step}`:"no step yet");break;case"redirect":t.push(e.url||"no address yet");break;case"celebrate":t.push(ae(bt,e.theme||"auto"),`${e.intensity||"medium"} intensity`);break;case"run-action":t.push(e.slug?fe.find(l=>l.slug===e.slug)?.title||e.slug:"no action chosen");break;case"notify":t.push(o(e.message)||"titled after the form",ae(Ce,e.level||"info"));break;default:break}return e.once&&t.push("only once"),t.filter(Boolean).join(" \xB7 ")}function De(e,t,{open:o=!1}={}){const n=document.createElement("div");n.className="fb-trig-action-row";const l=document.createElement("div");l.className="fb-trig-action-head",l.tabIndex=0,l.setAttribute("role","button");const a=document.createElement("span");a.className="fb-trig-chevron";const i=document.createElement("span");i.className="fb-trig-action-main";const r=document.createElement("strong"),c=document.createElement("small");c.className="fb-trig-action-summary",i.append(r,c);const s=document.createElement("span");s.className="fb-trig-kind",l.append(a,i,s,ze(()=>n.remove(),"Remove action"));const d=document.createElement("div");d.className="fb-trig-action-body";const u=J("fb-trig-action-type",de,e.type||"banner");d.appendChild(j("Action",u,{full:!0})),d.appendChild(Et(e,t));const b=()=>{const f=de.find(h=>h.value===u.value);r.textContent=f?.label||u.value,s.textContent=pn(f),s.classList.toggle("fb-trig-kind--event",f?.kind==="event"),c.textContent=mn(Tt(n))},y=f=>{d.hidden=!f,n.classList.toggle("is-open",f),l.setAttribute("aria-expanded",String(f)),A(a,f?"chevron-down":"chevron-right",12)};return l.addEventListener("click",()=>y(d.hidden)),l.addEventListener("keydown",f=>{f.target!==l||f.key!=="Enter"&&f.key!==" "||(f.preventDefault(),y(d.hidden))}),u.addEventListener("change",()=>{const f=Et({type:u.value},t);d.replaceChild(f,d.querySelector(".fb-trig-params")),b(),window.I?.scan?.()}),d.addEventListener("input",b),d.addEventListener("change",b),n.append(l,d),y(o),b(),n}function wt(e,t,o){const n=document.createElement("section");n.className=`fb-trig-sec fb-trig-sec--${e}`;const l=document.createElement("div");l.className="fb-trig-sec-head";const a=document.createElement("span");if(a.className="fb-trig-sec-title",a.textContent=t,l.appendChild(a),o){const i=document.createElement("span");i.className="fb-trig-sec-hint",i.textContent=o,l.appendChild(i)}return n.appendChild(l),n}function _e(e,t){const o=document.createElement("button");return o.type="button",o.className="btn btn-xs btn-ghost fb-trig-add",re(o,"plus",e,12),o.addEventListener("click",t),o}function kt(e,t,o,n){const a=wt(e,t,e==="then"?"What happens while the answers match.":"What happens while they do not. Leave empty for nothing.");a.classList.add("fb-trig-branch"),a.dataset.branch=e;const i=document.createElement("div");return i.className="fb-trig-action-list",(o||[]).forEach(r=>i.appendChild(De(r,n))),a.appendChild(i),a.appendChild(_e("Add action",()=>{i.appendChild(De({type:"banner"},n,{open:!0})),window.I?.scan?.()})),a}function St(e,t,o){const n=gt(t,!0),l=document.createElement("div");l.className="fb-trig-card",l.dataset.triggerId=e.id||`trg-${Date.now()}-${Math.random().toString(36).slice(2,7)}`;const a=document.createElement("div");a.className="fb-trig-card-head";const i=document.createElement("span");i.className="fb-trig-card-title",i.textContent=`Trigger ${o+1}`,a.appendChild(i);const r=pe("fb-trig-enabled",e.enabled!==!1,"Enabled","Untick to keep the trigger but switch it off.");if(a.appendChild(r),a.appendChild(ze(()=>l.remove(),"Remove trigger")),l.appendChild(a),!n.length){const u=document.createElement("p");return u.className="fb-trig-empty",u.textContent="Give this field a name first - a trigger tests an answer, and it needs something to test.",l.appendChild(u),l}const c=wt("when","When");c.querySelector(".fb-trig-sec-head").appendChild(J("fb-trig-match",[{value:"all",label:"all of these are true"},{value:"any",label:"any of these is true"}],Array.isArray(e.when?.any)?"any":"all"));const s=document.createElement("div");s.className="fb-trig-cond-list";const d=e.when?.all||e.when?.any||[];return(d.length?d:[{}]).forEach(u=>s.appendChild(yt(u,n))),c.appendChild(s),c.appendChild(_e("Add condition",()=>{s.appendChild(yt({},n)),window.I?.scan?.()})),l.appendChild(c),l.appendChild(kt("then","Then",e.then,t)),l.appendChild(kt("otherwise","Otherwise",e.otherwise,t)),l}function fn(e,t){const o=Array.isArray(e.triggers)?e.triggers:[],n=document.createElement("div");n.className="fb-field-triggers";const l=document.createElement("div");l.className="fb-trig-panel-head";const a=document.createElement("span");a.className="fb-trig-panel-title",a.textContent=`\u{1F3AF} Triggers${o.length?` (${o.length})`:""}`;const i=document.createElement("button");i.type="button",i.className="btn btn-xs btn-ghost",A(i,o.length?"chevron-down":"chevron-right",12);const r=document.createElement("div");r.className="fb-triggers-body",r.hidden=!o.length,l.addEventListener("click",()=>{r.hidden=!r.hidden,A(i,r.hidden?"chevron-right":"chevron-down",12)});const c=document.createElement("p");c.className="fb-trig-blurb",c.textContent="Do something to the form when the answers meet a condition - show a banner or toast, hide, disable or require other fields, refuse the submit, end the form early, notify the admins, jump to a step, or celebrate.",r.appendChild(c);const s=document.createElement("div");return s.className="fb-trig-list",o.forEach((d,u)=>s.appendChild(St(d,t,u))),r.appendChild(s),r.appendChild(_e("Add trigger",()=>{const d=St({enabled:!0,then:[]},t,s.children.length);s.appendChild(d),d.querySelector('.fb-trig-branch[data-branch="then"] .fb-trig-action-list')?.appendChild(De({type:"banner"},t,{open:!0})),window.I?.scan?.()})),l.appendChild(a),l.appendChild(i),n.appendChild(l),n.appendChild(r),n}function Tt(e){const t=e.querySelector(".fb-trig-action-type")?.value,o=de.find(a=>a.value===t);if(!o)return null;const n={type:t},l=a=>e.querySelector(a)?.value?.trim()||"";for(const a of o.params)switch(a){case"message":n.message=l(".fb-trig-p-message");break;case"finishLabel":{const i=l(".fb-trig-p-finish");i&&(n.finishLabel=i);break}case"doneMessage":{const i=l(".fb-trig-p-done");i&&(n.doneMessage=i);break}case"finish":e.querySelector(".fb-trig-p-finish-on")?.checked||(n.finish=!1);break;case"record":e.querySelector(".fb-trig-p-record")?.checked||(n.record=!1);break;case"level":n.level=l(".fb-trig-p-level")||"info";break;case"position":n.position=l(".fb-trig-p-position")||"field-below";break;case"dismissible":n.dismissible=!!e.querySelector(".fb-trig-p-dismissible")?.checked;break;case"target":{const i=l(".fb-trig-p-target")||"rest";n.target=i,i==="listed"&&(n.fields=Array.from(e.querySelectorAll(".fb-trig-p-fields option")).filter(r=>r.selected).map(r=>r.value));break}case"step":{const i=parseInt(l(".fb-trig-p-step"),10);Number.isFinite(i)&&(n.step=i);break}case"url":n.url=l(".fb-trig-p-url");break;case"theme":n.theme=l(".fb-trig-p-theme")||"auto";break;case"intensity":n.intensity=l(".fb-trig-p-intensity")||"medium";break;case"slug":n.slug=l(".fb-trig-p-slug");break;default:break}return o.kind==="event"&&!o.noOnce&&e.querySelector(".fb-trig-p-once")?.checked&&(n.once=!0),n}function hn(e){const t=Tt(e);if(!t)return null;const o=de.find(a=>a.value===t.type),n=t.type,l=["block-submit","end-form","notify"].includes(o.value);return o.params.includes("message")&&!t.message&&!l?(delete t.message,null):(l&&!t.message&&delete t.message,n==="redirect"&&!t.url||n==="run-action"&&!t.slug?null:t)}function bn(e){const o=document.querySelector(`.fb-field-card[data-index="${e}"]`)?.querySelector(".fb-field-triggers");if(!o)return;const n=[];return o.querySelectorAll(".fb-trig-card").forEach(l=>{const a=Array.from(l.querySelectorAll(".fb-trig-cond-row")).map(u=>{const b=u.querySelector(".fb-trig-cond-field")?.value,y=u.querySelector(".fb-trig-cond-op")?.value;if(!b||!y)return null;const f=u.querySelector(".fb-trig-cond-val")?.value??"";return{field:b,operator:y,value:f}}).filter(Boolean);if(!a.length)return;const i=u=>Array.from(l.querySelectorAll(`.fb-trig-branch[data-branch="${u}"] .fb-trig-action-row`)).map(hn).filter(Boolean),r=i("then"),c=i("otherwise");if(!r.length&&!c.length)return;const s=l.querySelector(".fb-trig-match")?.value==="any"?"any":"all",d={id:l.dataset.triggerId,enabled:!!l.querySelector(".fb-trig-enabled")?.checked,when:{[s]:a}};r.length&&(d.then=r),c.length&&(d.otherwise=c),n.push(d)}),n.length?n:void 0}function Nt(e,t,o){const n=document.createElement("div");return n.className="fb-field-extras",e==="chooser"?(n.appendChild(vn(t,o)),n.appendChild(Cn(t.options||[],o))):(Fe.has(e)&&n.appendChild(Tn(t.options||[],o)),e==="textarea"&&n.appendChild(_([V("Rows",`fb-rows-${o}`,"number",t.formConfig?.rows||4,"Height of textarea")])),(e==="string"||e==="textarea")&&n.appendChild(_([V("Min Length",`fb-minlength-${o}`,"number",t.minLength||"",""),V("Max Length",`fb-maxlength-${o}`,"number",t.maxLength||"","")])),e==="number"&&n.appendChild(_([V("Min",`fb-min-${o}`,"number",t.min??"",""),V("Max",`fb-max-${o}`,"number",t.max??"","")]))),Lt.includes(e)&&n.appendChild(yn(t,o)),n.children.length?n:null}const Lt=["string","email","tel","number","url","date","time","password","select","textarea"];function gn(e){return e?typeof e=="string"?{icon:"",text:e}:{icon:e.icon||"",text:e.icon?"":e.text??""}:{icon:"",text:""}}function yn(e,t){const o=document.createElement("div");o.className="fb-addons";const n=(a,i)=>{const r=gn(e.formConfig?.[a]),c=document.createElement("div");c.style.flex="1";const s=document.createElement("span");s.className="form-label",s.style.fontSize=".8rem",s.textContent=i;const d=et("Icon, e.g. search",r.icon);d.input.id=`fb-${a}-icon-${t}`,d.input.setAttribute("aria-label",`${i}: icon`);const u=document.createElement("input");return u.type="text",u.className="form-input",u.id=`fb-${a}-text-${t}`,u.value=r.text,u.placeholder="or text, e.g. \xA3 or kg",u.maxLength=24,u.setAttribute("aria-label",`${i}: text`),u.style.marginTop=".35rem",d.input.addEventListener("input",()=>{d.input.value.trim()&&(u.value="")}),u.addEventListener("input",()=>{u.value.trim()&&(d.input.value="")}),c.append(s,d.el,u),c};o.appendChild(_([n("prefix","Before the field"),n("suffix","After the field")]));const l=document.createElement("p");return l.className="form-hint text-muted",l.style.cssText="font-size:.73rem;margin:-.4rem 0 .75rem;",l.textContent="An icon or a short text joined to the start or end of the input, like \xA3 before a price or kg after a weight. Leave both empty for none.",o.appendChild(l),o}function qt(e){const t=document.createElement("div");t.className="fb-chooser-section",t.style.cssText="border:1px solid var(--dm-border); border-radius:6px; margin-top:.5rem; overflow:hidden;";const o=document.createElement("div");o.style.cssText="padding:.4rem .6rem; background:color-mix(in srgb, var(--dm-background) 60%, transparent); font-weight:600; font-size:.82rem; border-bottom:1px solid var(--dm-border);",o.textContent=e,t.appendChild(o);const n=document.createElement("div");return n.className="fb-chooser-section-body",n.style.cssText="padding:.55rem .6rem; display:flex; flex-direction:column; gap:.45rem;",t.appendChild(n),{wrap:t,body:n}}function Ee(e,t,o,n,l){const a=document.createElement("div");a.style.flex="1";const i=document.createElement("label");i.htmlFor=e,i.className="form-label",i.textContent=t,i.style.fontSize=".8rem";const r=document.createElement("select");if(r.id=e,r.className="form-input",n.forEach(([c,s])=>{const d=document.createElement("option");d.value=c,d.textContent=s,String(c)===String(o)&&(d.selected=!0),r.appendChild(d)}),a.appendChild(i),a.appendChild(r),l){const c=document.createElement("p");c.className="form-hint text-muted",c.textContent=l,c.style.cssText="font-size:.72rem;margin-top:.2rem;",a.appendChild(c)}return a}function $t(e,t,o,n){const l=document.createElement("div");l.style.flex="1";const a=document.createElement("label");a.style.cssText="display:inline-flex;align-items:center;gap:.4rem;font-size:.82rem;";const i=document.createElement("input");if(i.id=e,i.type="checkbox",i.checked=!!o,a.appendChild(i),a.appendChild(document.createTextNode(" "+t)),l.appendChild(a),n){const r=document.createElement("p");r.className="form-hint text-muted",r.textContent=n,r.style.cssText="font-size:.72rem;margin-top:.2rem;",l.appendChild(r)}return l}function Pe(e,t,o,n,l){const a=document.createElement("div");a.style.flex="1";const i=document.createElement("label");i.htmlFor=e,i.className="form-label",i.textContent=t,i.style.fontSize=".8rem",a.appendChild(i);const r=document.createElement("div");r.style.cssText="display:flex;gap:.4rem;align-items:stretch;";const c=document.createElement("input");c.id=e,c.type="text",c.className="form-input",c.value=o||"",n&&(c.placeholder=n),c.style.flex="1";const s=document.createElement("input");s.type="color",s.title="Pick a colour",s.style.cssText="width:36px;height:auto;padding:0;border:1px solid var(--dm-border);border-radius:4px;cursor:pointer;";const d=String(c.value).match(/^#[0-9a-fA-F]{3,8}$/);if(s.value=d?c.value:"#000000",s.addEventListener("input",()=>{c.value=s.value,c.dispatchEvent(new Event("input",{bubbles:!0}))}),r.appendChild(c),r.appendChild(s),a.appendChild(r),l){const u=document.createElement("p");u.className="form-hint text-muted",u.textContent=l,u.style.cssText="font-size:.72rem;margin-top:.2rem;",a.appendChild(u)}return a}function vn(e,t){const o=document.createElement("div");o.style.cssText="margin-top:.5rem; display:flex; flex-direction:column; gap:.4rem;";const n=qt("Layout");n.body.appendChild(_([Ee(`fb-chooser-variant-${t}`,"Variant",e.variant||"card",[["card","Card - rich tiles"],["chip","Chip - compact pills"]],"Card supports description and grid columns; chips wrap."),$t(`fb-chooser-multiple-${t}`,"Multi-select",!!e.multiple,"Off = single (radio); on = multi (checkbox).")])),n.body.appendChild(_([Ee(`fb-chooser-density-${t}`,"Density",e.density||"comfortable",[["comfortable","Comfortable"],["compact","Compact"]],"Compact strips description (card variant only)."),V("Columns",`fb-chooser-columns-${t}`,"number",e.columns||3,"Card grid (1-6). Ignored for chips.")])),o.appendChild(n.wrap);const l=qt("Visual");return l.body.appendChild(_([Pe(`fb-chooser-accent-${t}`,"Accent",e.accent||"","primary / success / #ec4899","Selected highlight colour - semantic name or any CSS colour."),Ee(`fb-chooser-accent-style-${t}`,"Accent style",e.accentStyle||"border",[["border","Border (default)"],["solid","Solid"],["glow","Glow"],["overlay","Overlay"],["underline","Underline"]],"Visual treatment of the selected state.")])),l.body.appendChild(_([$t(`fb-chooser-glow-${t}`,"Glow on selected",!!e.glow,"Soft outer glow."),Pe(`fb-chooser-glow-colour-${t}`,"Glow colour",e.glowColour||"","defaults to accent","Optional; semantic name or CSS colour.")])),l.body.appendChild(_([Ee(`fb-chooser-shadow-${t}`,"Shadow",e.shadow||"none",[["none","None"],["sm","Small"],["md","Medium"],["lg","Large"],["xl","Extra Large"]],"Drop shadow on every option."),Pe(`fb-chooser-shadow-colour-${t}`,"Shadow colour",e.shadowColour||"","rgba(0,0,0,0.1)","Optional shadow tint.")])),o.appendChild(l.wrap),o}function xn(e,t,o,n){const l=document.createElement("div");l.className="fb-chooser-option-row",l.dataset.optIdx=String(o),l.style.cssText="border:1px solid var(--dm-border); border-radius:4px; padding:.5rem; background:var(--dm-background-alt); display:flex; flex-direction:column; gap:.35rem;";const a=document.createElement("div");a.style.cssText="display:flex; gap:.4rem; align-items:center;";const i=document.createElement("div");i.style.cssText="display:flex; flex-direction:column; gap:1px;";const r=document.createElement("button");r.type="button",r.className="btn btn-xs btn-ghost",r.title="Move up",A(r,"arrow-up"),r.addEventListener("click",O=>{O.preventDefault(),At(t,o,-1,n)});const c=document.createElement("button");c.type="button",c.className="btn btn-xs btn-ghost",c.title="Move down",A(c,"arrow-down"),c.addEventListener("click",O=>{O.preventDefault(),At(t,o,1,n)}),i.appendChild(r),i.appendChild(c),a.appendChild(i);const s=document.createElement("input");s.type="text",s.className="form-input fb-chooser-opt-value",s.placeholder="value",s.value=e.value||"",s.style.flex="1",a.appendChild(s);const d=document.createElement("input");d.type="text",d.className="form-input fb-chooser-opt-label",d.placeholder="label (visible to users)",d.value=e.label||"",d.style.flex="2",a.appendChild(d);const u=document.createElement("button");u.type="button",u.className="btn btn-xs btn-ghost",u.style.cssText="line-height:1;opacity:.6;",u.title="Remove option",A(u,"x"),u.addEventListener("click",O=>{O.preventDefault(),kn(t,o,n)}),a.appendChild(u),l.appendChild(a);const b=document.createElement("div");b.style.cssText="display:flex; gap:.4rem;";const y=et("icon name (optional)",e.icon||"");y.input.classList.add("fb-chooser-opt-icon"),y.el.style.flex="1",b.appendChild(y.el);const f=document.createElement("input");f.type="text",f.className="form-input fb-chooser-opt-description",f.placeholder="description (card + comfortable only)",f.value=e.description||"",f.style.flex="2",b.appendChild(f),l.appendChild(b);const h=document.createElement("div");h.style.cssText="display:flex; gap:.4rem;";const k=document.createElement("input");k.type="text",k.className="form-input fb-chooser-opt-tooltip",k.placeholder="tooltip (hover hint)",k.value=e.tooltip||"",k.style.flex="2",h.appendChild(k);const m=document.createElement("input");m.type="text",m.className="form-input fb-chooser-opt-badge-text",m.placeholder="badge text",m.value=e.badge?.text||"",m.style.flex="1",h.appendChild(m);const x=document.createElement("select");x.className="form-input fb-chooser-opt-badge-type",x.style.flex="1",[["","no badge"],["primary","primary"],["success","success"],["info","info"],["warning","warning"],["danger","danger"]].forEach(([O,X])=>{const W=document.createElement("option");W.value=O,W.textContent=X,(e.badge?.type||"")===O&&(W.selected=!0),x.appendChild(W)}),h.appendChild(x),l.appendChild(h);const N=document.createElement("div");N.style.cssText="display:flex; gap:1rem; font-size:.8rem;";const T=document.createElement("label");T.style.cssText="display:inline-flex;align-items:center;gap:.3rem;";const I=document.createElement("input");I.type="checkbox",I.className="fb-chooser-opt-recommended",I.checked=!!e.recommended,T.appendChild(I),T.appendChild(document.createTextNode(" Recommended")),N.appendChild(T);const B=document.createElement("label");B.style.cssText="display:inline-flex;align-items:center;gap:.3rem;";const G=document.createElement("input");return G.type="checkbox",G.className="fb-chooser-opt-disabled",G.checked=!!e.disabled,B.appendChild(G),B.appendChild(document.createTextNode(" Disabled")),N.appendChild(B),l.appendChild(N),l}function Cn(e,t){const o=document.createElement("div");o.style.cssText="margin-top:.5rem;";const n=document.createElement("div");n.style.cssText="display:flex; align-items:center; justify-content:space-between; margin-bottom:.4rem;";const l=document.createElement("p");l.textContent="Options",l.style.cssText="font-size:.85rem;font-weight:600;margin:0;",n.appendChild(l);const a=document.createElement("button");a.type="button",a.className="btn btn-xs btn-ghost",re(a,"plus","Add option"),n.appendChild(a),o.appendChild(n);const i=document.createElement("div");i.className="fb-chooser-options-list",i.id=`fb-chooser-options-${t}`,i.style.cssText="display:flex; flex-direction:column; gap:.4rem;",o.appendChild(i);const r=()=>{i.replaceChildren();const c=En(t);(c.length?c:e||[]).forEach((d,u)=>{i.appendChild(xn(d,t,u,r))})};return a.addEventListener("click",c=>{c.preventDefault(),wn(t,r)}),r(),o}function En(e){const t=w[e];return!t||!Array.isArray(t.options)?[]:t.options}function je(e){return w[e]?(Array.isArray(w[e].options)||(w[e].options=[]),w[e].options):[]}function wn(e,t){we(e),je(e).push({value:"new",label:"New option"}),t()}function kn(e,t,o){we(e),je(e).splice(t,1),o()}function At(e,t,o,n){we(e);const l=je(e),a=t+o;if(a<0||a>=l.length)return;const[i]=l.splice(t,1);l.splice(a,0,i),n()}function we(e){const t=document.getElementById(`fb-chooser-options-${e}`);if(!t)return;const o=t.querySelectorAll(".fb-chooser-option-row"),n=[];o.forEach(l=>{const a={value:l.querySelector(".fb-chooser-opt-value")?.value.trim()||"",label:l.querySelector(".fb-chooser-opt-label")?.value.trim()||""},i=l.querySelector(".fb-chooser-opt-icon")?.value.trim();i&&(a.icon=i);const r=l.querySelector(".fb-chooser-opt-description")?.value.trim();r&&(a.description=r);const c=l.querySelector(".fb-chooser-opt-tooltip")?.value.trim();c&&(a.tooltip=c);const s=l.querySelector(".fb-chooser-opt-badge-text")?.value.trim(),d=l.querySelector(".fb-chooser-opt-badge-type")?.value;(s||d)&&(a.badge={},s&&(a.badge.text=s),d&&(a.badge.type=d)),l.querySelector(".fb-chooser-opt-recommended")?.checked&&(a.recommended=!0),l.querySelector(".fb-chooser-opt-disabled")?.checked&&(a.disabled=!0),n.push(a)}),w[e]&&(w[e].options=n)}function _(e){const t=document.createElement("div");return t.style.cssText="display:flex;gap:.85rem;margin-bottom:.75rem;",e.forEach(o=>{o&&t.appendChild(o)}),t}function V(e,t,o,n,l){const a=document.createElement("div");a.style.flex="1";const i=document.createElement("label");i.htmlFor=t,i.className="form-label",i.textContent=e,i.style.fontSize=".8rem";const r=document.createElement("input");if(r.id=t,r.type=o||"text",r.className="form-input",r.value=n??"",a.appendChild(i),a.appendChild(r),l){const c=document.createElement("p");c.className="form-hint text-muted",c.textContent=l,c.style.cssText="font-size:.73rem;margin-top:.35rem;",a.appendChild(c)}return a}function Sn(e,t,o,n){const l=document.createElement("div");l.style.flex="1";const a=document.createElement("label");a.htmlFor=t,a.className="form-label",a.textContent=e,a.style.fontSize=".8rem";const i=document.createElement("select");return i.id=t,i.className="form-select",o.forEach(r=>{const c=document.createElement("option");c.value=r.value,c.textContent=r.label,r.value===n&&(c.selected=!0),i.appendChild(c)}),l.appendChild(a),l.appendChild(i),l}function Ft(e,t,o){const n=document.createElement("div");n.style.cssText="flex:0;min-width:80px;display:flex;flex-direction:column;justify-content:flex-end;";const l=document.createElement("label");l.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;font-size:.8rem;white-space:nowrap;";const a=document.createElement("input");return a.id=t,a.type="checkbox",a.checked=o,l.appendChild(a),l.appendChild(document.createTextNode(e)),n.appendChild(l),n}function Tn(e,t){const o=document.createElement("div");o.style.cssText="margin-top:.4rem;";const n=document.createElement("p");n.textContent="Options (one per line: value or value:Label)",n.style.cssText="font-size:.8rem;font-weight:600;margin-bottom:.3rem;";const l=document.createElement("textarea");return l.id=`fb-options-${t}`,l.className="form-input",l.rows=4,l.placeholder=`yes:Yes
6
6
  no:No
7
7
  maybe:Maybe`,l.value=(e||[]).map(a=>{const i=typeof a=="string"?a:a.value??"",r=typeof a=="string"?a:a.label??i;return i===r?i:`${i}:${r}`}).join(`
8
- `),l.style.fontFamily="monospace",o.appendChild(n),o.appendChild(l),o}function ke(e){return parseInt(e,10)===3?3:2}function Se(e){const t=e.find("#field-project").val()||"";return z(),{title:e.find("#field-title").val().trim(),slug:e.find("#field-slug").val().trim(),description:e.find("#field-description").val().trim(),...e.find("#field-bundled").is(":checked")?{bundled:!0}:{},meta:{...be||{},project:t||null},fields:C,settings:{submitText:e.find("#setting-submit-text").val().trim()||"Submit",successMessage:e.find("#setting-success-message").val().trim()||"Thank you.",layout:e.find("#setting-layout").val()||"grid",columns:ke(e.find("#setting-columns").val()),submitSpan:e.find("#setting-submit-span").val()||"",honeypot:e.find("#setting-honeypot").prop("checked"),rateLimitPerMinute:parseInt(e.find("#setting-rate-limit").val(),10)||3,successRedirect:e.find("#setting-success-redirect").val().trim()||null,actionSlug:e.find("#action-cms-slug").val()||null},actions:{email:{enabled:e.find("#action-email-enabled").prop("checked"),recipients:e.find("#action-email-recipients").val().trim(),subjectPrefix:e.find("#action-email-subject-prefix").val().trim()},webhook:{enabled:e.find("#action-webhook-enabled").prop("checked"),url:e.find("#action-webhook-url").val().trim(),method:e.find("#action-webhook-method").val()},...he!==null&&{collection:he}}}}function Lt(e,t){const o={};return e.forEach(n=>{if(n.type==="page-break"||n.type==="spacer")return;const l={...n.formConfig||{}};l.span==="full"&&t&&(l.span=t);const a=n.type==="chooser"||!!(n.variant&&["select","radio","checkbox-group"].includes(n.type)),r={type:n.type==="checkbox"?"boolean":n.type==="date"||n.type==="icon"||n.type==="image"?"string":a?"chooser":n.type,label:n.label,required:n.required,options:n.options,formConfig:{...n.placeholder&&{placeholder:n.placeholder},...n.helper&&{helperText:n.helper},...n.tooltip&&{tooltip:n.tooltip},...l}};a&&(n.variant&&(r.variant=n.variant),(n.multiple||n.type==="checkbox-group")&&(r.multiple=!0),n.density&&(r.density=n.density),n.columns&&(r.columns=n.columns),n.accent&&(r.accent=n.accent),n.accentStyle&&(r.accentStyle=n.accentStyle),n.glow&&(r.glow=!0),n.glowColour&&(r.glowColour=n.glowColour),n.shadow&&(r.shadow=n.shadow),n.shadowColour&&(r.shadowColour=n.shadowColour)),o[n.name]=r}),o}function qt(e,t){const o=typeof e=="string"?document.querySelector(e):e;o&&(t||[]).forEach(n=>{if(n.type!=="date"||!n.name)return;const l=o.querySelector(`[name="${n.name}"]`);l&&l.type!=="date"&&(l.type="date")})}function $t(e,t){const o=e?.querySelector?.(".dm-plugin-banner-name .dm-plugin-banner-sep");if(o){for(;o.nextSibling;)o.nextSibling.remove();o.after(document.createTextNode(String(t||"")))}}const yn=e=>String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");let W=null,At=0,Ft=!1;const Te=e=>String(e||"").split("?")[0].replace(/\/$/,"");function vn(){Ft||!window.R?.use||(Ft=!0,R.use((e,t,o)=>!W||!lt(t?.path,e?.path)||W.path!==Te(t?.path)||(W.flush(),!W.dirty())?o():E.confirm("You have unsaved changes to this form. Leave anyway? They are kept as a draft for your next visit to it.").then(n=>{n?o():history.replaceState(null,"",`#${t.path}`)})),M.subscribe("router:afterChange",({to:e,from:t}={})=>{if(W&&W.path===Te(t?.path)&&lt(t?.path,e?.path)){const o=W;W=null,o.dispose()}}))}export const formEditorView={templateUrl:"/admin/js/templates/form-editor.html",async onMount(e){if(vn(),W){const p=W;W=null,p.dispose()}const t=Te(location.hash.replace(/^#/,"")),o=++At,n=()=>o!==At||!e.get(0)?.isConnected||Te(location.hash.replace(/^#/,""))!==t;C=[],N=null,he=null;const l=window.location.hash.match(/\/forms\/edit\/([^/?#]+)/);N=l?l[1]:null;let a=null;const i=E.loader(e.get(0),{type:"dots"});if(N)try{if(a=await ie(`/forms/${N}`),n()){i.destroy();return}C=a.fields||[],he=a.actions?.collection??null}catch{if(n()){i.destroy();return}E.toast("Could not load form.",{type:"error"})}be={};try{const p=await ie("/projects").catch(()=>[]);if(n()){i.destroy();return}const v=e.find("#field-project").get(0);v&&(Array.isArray(p)?p:[]).forEach(g=>{const w=document.createElement("option");w.value=g.slug,w.textContent=g.name||g.slug,v.appendChild(w)})}catch{}if(i.destroy(),$t(e.get(0),a?a.title||a.slug:"New form"),N||e.find("#field-title").get(0).addEventListener("input",function(){e.find("#field-slug").val(st(this.value))}),E.tabs(e.find("#editor-tabs").get(0)),a){e.find("#field-title").val(a.title),e.find("#field-slug").val(a.slug),e.find("#field-description").val(a.description||""),e.find("#field-bundled").prop("checked",!!a.bundled),be=a.meta||{},e.find("#field-project").val(a.meta?.project||"");const p=a.settings||{};e.find("#setting-submit-text").val(p.submitText||"Submit"),e.find("#setting-success-message").val(p.successMessage||""),e.find("#setting-layout").val(p.layout||"grid"),e.find("#setting-columns").val(ke(p.columns)),e.find("#setting-submit-span").val(p.submitSpan||""),e.find("#columns-group").get(0).style.display=(p.layout||"grid")==="grid"?"":"none",e.find("#setting-honeypot").prop("checked",p.honeypot!==!1),e.find("#setting-rate-limit").val(p.rateLimitPerMinute||3),e.find("#setting-success-redirect").val(p.successRedirect||"");const v=a.actions?.email||{};e.find("#action-email-enabled").prop("checked",v.enabled||!1),e.find("#action-email-recipients").val(v.recipients||""),e.find("#action-email-subject-prefix").val(v.subjectPrefix||"");const g=a.actions?.webhook||{};e.find("#action-webhook-enabled").prop("checked",g.enabled||!1),e.find("#action-webhook-url").val(g.url||""),e.find("#action-webhook-method").val(g.method||"POST")}const r=a?.settings?.actionSlug||"";try{const p=await ie("/actions").catch(()=>[]);if(n())return;const v=e.find("#action-cms-slug").get(0);if(fe=Array.isArray(p)?p:[],(Array.isArray(p)?p:[]).forEach(g=>{const w=document.createElement("option");w.value=g.slug,w.textContent=g.title||g.slug,g.slug===r&&(w.selected=!0),v.appendChild(w)}),!p.length){const g=document.createElement("option");g.value="",g.textContent="No actions available",g.disabled=!0,v.appendChild(g)}}catch{}const c=p=>{e.find("#columns-group").get(0).style.display=p?"":"none",document.querySelectorAll(".fb-grid-row").forEach(v=>{v.style.display=p?"flex":"none"})};e.find("#setting-layout").get(0)?.addEventListener("change",function(){c(this.value==="grid")}),c(e.find("#setting-layout").val()==="grid"),Q(e);const s=e.find("#add-element-menu").get(0);e.find("#add-element-btn").get(0).addEventListener("click",p=>{p.stopPropagation(),s.style.display=s.style.display==="none"?"":"none"}),ne&&document.removeEventListener("click",ne),ne=()=>{s&&(s.style.display="none")},document.addEventListener("click",ne),e.find("#add-field-btn").get(0).addEventListener("click",()=>{s&&(s.style.display="none"),z();const p=C.length;C.push({name:`field_${p+1}`,type:"string",label:"New Field",required:!1,placeholder:""}),Q(e);const v=e.find("#fields-list").get(0)?.lastElementChild;if(v){const g=v.querySelector(".fb-field-body");g&&(g.style.display="")}}),e.find("#add-spacer-btn").get(0).addEventListener("click",()=>{s&&(s.style.display="none"),z(),C.push({type:"spacer"}),Q(e)}),e.find("#add-page-break-btn").get(0).addEventListener("click",()=>{s&&(s.style.display="none"),z();const p=C.filter(v=>v.type==="page-break").length+2;C.push({type:"page-break",label:`Step ${p}`,description:""}),Q(e)}),ge=()=>{const p=Se(e),v=e.find("#preview-container").get(0);if(!v)return;const g=e.find("#preview-test-result").get(0),w=e.find("#preview-test-badge").get(0);g&&(g.style.display="none",g.textContent=""),w&&(w.style.display=N?"":"none"),e.find("#preview-card").get(0).style.display="",v.textContent="";const B=document.createElement("div");B.id="fb-preview-form",v.appendChild(B);const U=N?async L=>{g&&(g.style.display="none",g.textContent="");try{const T=await fetch(`/api/forms/submit/${N}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(L)}),K=await T.json();if(!T.ok)throw new Error(K.error||"Submission failed.");g&&(g.textContent=K.message||p.settings?.successMessage||"Submitted successfully.",g.style.cssText="display:block;margin-top:.75rem;padding:.6rem .9rem;border-radius:6px;font-size:.9rem;background:color-mix(in srgb, var(--dm-success) 12%, transparent);color:var(--dm-success);"),E.toast("Test submission stored.",{type:"success"})}catch(T){g&&(g.textContent=T.message,g.style.cssText="display:block;margin-top:.75rem;padding:.6rem .9rem;border-radius:6px;font-size:.9rem;background:color-mix(in srgb, var(--dm-danger) 12%, transparent);color:var(--dm-danger);"),E.toast(T.message,{type:"error"})}return!1}:()=>!1,ue=p.fields.some(L=>L.type==="page-break");if(B.addEventListener("dm-form-end",L=>{const T=L.detail?.validation;if(T&&!T.valid){E.toast(`Still to answer: ${T.missing.map(K=>K.label).concat(T.errors).join(", ")}`,{type:"warning"});return}U(L.detail?.values||{})}),typeof F<"u"){const L=ke(p.settings?.columns),T=p.settings?.layout||"grid";if(ue&&F.wizard){const K=zt(p.fields,p.title),me=K.map(te=>({title:te.title,description:te.description,fields:Lt(te.fields,L)}));let O=0,Z=null,Le=null;const Xe=te=>{const Ze=(K[O]||{}).fields||[];qt("#fb-preview-form",Ze),et(B,Ze),te&&window.FormLogicEngine&&ct(p.fields)&&(Z?.destroy?.(),Z=new window.FormLogicEngine.FormLogicRuntime({fields:p.fields},B,{baseValues:Le?.getStepData?Le.getStepData():{}}),Z.init())};Le=F.wizard("#fb-preview-form",{schema:{steps:me},layout:T,columns:L,finishText:p.settings?.submitText||"Finish",onStepChange:te=>{O=te,queueMicrotask(()=>Xe(!0))},onSubmit:U}),Xe(!1)}else if(F.render){const K=Lt(p.fields,L),me={};if(p.fields.forEach(O=>{if(!(!O.name||O.type==="page-break"||O.type==="spacer")&&(O.type==="select"||O.type==="multiselect")&&O.required){const Z=(O.options||[])[0];Z&&(me[O.name]=typeof Z=="object"?Z.value:Z)}}),F.render("#fb-preview-form",K,me,{submitText:p.settings?.submitText||"Submit",layout:T,columns:L,onSubmit:U}),T==="grid"&&p.settings?.submitSpan==="full"){const O=document.querySelector("#fb-preview-form .form-buttons");O&&O.classList.add("col-span-full")}qt("#fb-preview-form",p.fields),et(B,p.fields)}window.FormLogicEngine&&ct(p.fields)&&requestAnimationFrame(()=>{new window.FormLogicEngine.FormLogicRuntime({fields:p.fields},B).init()})}else{const L=document.createElement("p");L.textContent=`${p.fields.filter(T=>T.type!=="page-break").length} field(s): ${p.fields.filter(T=>T.type!=="page-break").map(T=>T.label).join(", ")}`,L.style.cssText="color:var(--dm-text-muted);font-style:italic;",B.appendChild(L)}},ge();const d=e.find("#editor-tabs").get(0),u=e.find("#preview-card").get(0),b=p=>{u&&u.contains(p.target)||Oe()};d&&(d.addEventListener("input",b),d.addEventListener("change",b));const y=e.get(0),m=[],f=p=>(m.push(p),p);let k=!1;const h=M.observable(!1),x=M.observable(!1);let $=N,A=Se(e),G=Ae(A),_=A;const j=()=>{k||!y.isConnected||(_=Se(e),h.value=Ae(_)!==G)};let I=null;Be=()=>{clearTimeout(I),I=setTimeout(j,150)},f(()=>{clearTimeout(I),Be=null,ce=null});const X=p=>{const v=p.settings||{};e.find("#field-title").val(p.title||""),e.find("#field-slug").val(p.slug||""),e.find("#field-description").val(p.description||""),e.find("#field-bundled").prop("checked",!!p.bundled),be={...p.meta||{}},e.find("#field-project").val(p.meta?.project||""),e.find("#setting-submit-text").val(v.submitText||"Submit"),e.find("#setting-success-message").val(v.successMessage||""),e.find("#setting-layout").val(v.layout||"grid"),e.find("#setting-columns").val(ke(v.columns)),e.find("#setting-submit-span").val(v.submitSpan||""),e.find("#setting-honeypot").prop("checked",v.honeypot!==!1),e.find("#setting-rate-limit").val(v.rateLimitPerMinute||3),e.find("#setting-success-redirect").val(v.successRedirect||""),e.find("#action-cms-slug").val(v.actionSlug||"");const g=p.actions?.email||{};e.find("#action-email-enabled").prop("checked",!!g.enabled),e.find("#action-email-recipients").val(g.recipients||""),e.find("#action-email-subject-prefix").val(g.subjectPrefix||"");const w=p.actions?.webhook||{};e.find("#action-webhook-enabled").prop("checked",!!w.enabled),e.find("#action-webhook-url").val(w.url||""),e.find("#action-webhook-method").val(w.method||"POST"),C=JSON.parse(JSON.stringify(p.fields||[])),c((v.layout||"grid")==="grid"),Q(e)};async function H(){if(x.peek()||k)return;const p=Se(e);if(!p.title){E.toast("Please enter a form title (Form Details).",{type:"error"});return}x.value=!0;const v=N;try{N?(await ie(`/forms/${N}`,{method:"PUT",body:JSON.stringify(p)}),E.toast("Form saved.",{type:"success"})):(N=(await ie("/forms",{method:"POST",body:JSON.stringify(p)})).slug,E.toast("Form created.",{type:"success"})),$=N,A=p,G=Ae(p),_=p,h.value=!1;for(const g of new Set([se(v),se(N)]))try{S.remove?S.remove(g):S.set(g,null)}catch{}$t(y,p.title),Pt("#/forms"),v||R.navigate(`/forms/edit/${N}`)}catch(g){v||(N=null),E.toast(g.message||"Failed to save form.",{type:"error"})}finally{x.value=!1}}async function It(){h.peek()&&(!await E.confirm("Discard the changes you have not saved?")||k||(X(A),j(),Ye()))}const Ve=y.querySelector(".fex-bar");if(Ve){const p=M.applyBindings({dirty:h,saving:x},Ve,{methods:{save:H,discard:It}});f(()=>p.dispose())}f(Dt(y,{position:"bottom"})),qe(e,{id:"form-editor-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>H()}),qe(e,{id:"form-editor-back",icon:"arrow-left",title:"All forms",onClick:()=>{location.hash="#/forms"}}),N&&qe(e,{id:"form-editor-subs",icon:"list",title:"Submissions",onClick:()=>{location.hash=`#/forms/${encodeURIComponent(N)}/submissions`}});const Ge=p=>{n()||!(p.ctrlKey||p.metaKey)||String(p.key).toLowerCase()!=="s"||(p.preventDefault(),p.stopImmediatePropagation(),H())};document.addEventListener("keydown",Ge,!0),f(()=>document.removeEventListener("keydown",Ge,!0));const Y=e.find("#fields-list").get(0),Bt=p=>p?.closest?.(".fb-field-card"),We=p=>Number(p?.dataset.index),He=p=>p?.querySelector(":scope > .fb-field-body"),Ue=p=>{const v=He(p);v&&(v.style.display=v.style.display==="none"?"":"none")},oe=(p,v)=>{C=p,Q(e),v!=null&&Y?.querySelectorAll(".fb-field-card")[v]?.focus()},Ot=async p=>{try{await navigator.clipboard.writeText(p),E.toast(`Copied "${p}".`,{type:"success"})}catch{E.toast(`Copy failed - the name is ${p}.`,{type:"info"})}},Rt=p=>{const v=Bt(p),g=We(v);z();const w=C[g];if(!w)return[];const B=w.type!=="spacer"&&w.type!=="page-break",U=He(v),ue=U&&U.style.display!=="none";return[{header:$e(w)},...U?[{label:ue?"Close":"Edit",icon:ue?"chevron-up":"edit",action:()=>Ue(v)}]:[],...B?[{label:"Logic & triggers",icon:"zap",action:()=>{U&&(U.style.display=""),(U?.querySelector(".fb-field-logic")||U)?.scrollIntoView({block:"center",behavior:"smooth"})}}]:[],{label:"Duplicate",icon:"copy",action:()=>{z(),oe(jt(C,g),g+1)}},{type:"divider"},{label:"Move up",icon:"arrow-up",shortcut:"Alt+\u2191",disabled:g===0,action:()=>{z(),oe(at(C,g,-1),g-1)}},{label:"Move down",icon:"arrow-down",shortcut:"Alt+\u2193",disabled:g>=C.length-1,action:()=>{z(),oe(at(C,g,1),g+1)}},...B&&w.name?[{label:"Copy field name",icon:"clipboard",action:()=>Ot(w.name)}]:[],{type:"divider"},{label:"Delete\u2026",icon:"trash",danger:!0,action:async()=>{if(w.type!=="spacer"&&!await E.confirm(`Remove "${yn($e(w))}"?`))return;z();const L=[...C];L.splice(g,1),oe(L,Math.min(g,L.length-1))}}]};if(Y&&typeof E.sortable=="function"){const p=E.sortable(Y,{items:".fb-field-card",handle:".fb-drag-grip",key:null,onSort:({from:v,to:g})=>{z();const w=[...C],[B]=w.splice(v,1);w.splice(g,0,B),oe(w,g)}});f(()=>p.destroy())}let Je=null;if(Y&&E.contextMenu&&(Je=E.contextMenu(Y,{cursor:"glyph",match:".fb-field-card > :first-child",inherit:!1,density:"compact",items:p=>Rt(p)}),f(()=>Je?.destroy?.())),ce=p=>{for(const v of p.querySelectorAll(".fb-field-card")){v.tabIndex=0;const g=v.firstElementChild;if(!g||g.querySelector(".fex-kebab"))continue;const w=document.createElement("button");w.type="button",w.className="fex-kebab",w.textContent="\u22EE",w.title="Actions",z(),w.setAttribute("aria-label",`Actions for ${$e(C[We(v)])}`),w.addEventListener("click",B=>{B.stopPropagation(),tt(w)}),g.appendChild(w)}},Y){ce(Y);const p=v=>{const g=v.target?.classList?.contains("fb-field-card")?v.target:null;g&&(v.key==="F10"&&v.shiftKey?(v.preventDefault(),tt(g.firstElementChild)):v.key==="Enter"&&(v.preventDefault(),Ue(g)))};Y.addEventListener("keydown",p),f(()=>Y.removeEventListener("keydown",p))}const Ye=()=>{try{const p=se($);S.remove?S.remove(p):S.set(p,null)}catch{}},Ke=p=>{if(!h.peek()||!p){Ye();return}try{S.set(se($),{data:_t(p),base:nt(G),at:new Date().toISOString()})}catch{}},Qe=p=>{j(),h.peek()&&(Ke(_),p.preventDefault(),p.returnValue="")};window.addEventListener("beforeunload",Qe),f(()=>window.removeEventListener("beforeunload",Qe)),f(()=>{ne&&(document.removeEventListener("click",ne),ne=null)}),W={path:t,dirty:()=>h.peek(),flush:()=>{clearTimeout(I),j()},dispose(){k||(Ke(_),k=!0,m.splice(0).reverse().forEach(p=>{try{p?.()}catch{}}))}};const Ne=se(N);let ee=null;try{ee=S.get(Ne)}catch{}if(ee?.data){try{S.remove?S.remove(Ne):S.set(Ne,null)}catch{}const p=ee.base&&ee.base!==nt(G),v=ee.at?` (${Gt(ee.at)})`:"";await E.confirm(`You left unsaved changes to this form${v}.${p?" The saved form has changed since then.":""} Pick up where you left off?`)&&!k&&(X(Vt(ee.data,A)),j())}}};
8
+ `),l.style.fontFamily="monospace",o.appendChild(n),o.appendChild(l),o}function ke(e){return parseInt(e,10)===3?3:2}function Se(e){const t=e.find("#field-project").val()||"";return D(),{title:e.find("#field-title").val().trim(),slug:e.find("#field-slug").val().trim(),description:e.find("#field-description").val().trim(),...e.find("#field-bundled").is(":checked")?{bundled:!0}:{},meta:{...be||{},project:t||null},fields:w,settings:{submitText:e.find("#setting-submit-text").val().trim()||"Submit",successMessage:e.find("#setting-success-message").val().trim()||"Thank you.",layout:e.find("#setting-layout").val()||"grid",columns:ke(e.find("#setting-columns").val()),submitSpan:e.find("#setting-submit-span").val()||"",honeypot:e.find("#setting-honeypot").prop("checked"),rateLimitPerMinute:parseInt(e.find("#setting-rate-limit").val(),10)||3,successRedirect:e.find("#setting-success-redirect").val().trim()||null,actionSlug:e.find("#action-cms-slug").val()||null},actions:{email:{enabled:e.find("#action-email-enabled").prop("checked"),recipients:e.find("#action-email-recipients").val().trim(),subjectPrefix:e.find("#action-email-subject-prefix").val().trim()},webhook:{enabled:e.find("#action-webhook-enabled").prop("checked"),url:e.find("#action-webhook-url").val().trim(),method:e.find("#action-webhook-method").val()},...he!==null&&{collection:he}}}}function It(e,t){const o={};return e.forEach(n=>{if(n.type==="page-break"||n.type==="spacer")return;const l={...n.formConfig||{}};l.span==="full"&&t&&(l.span=t);const a=n.type==="chooser"||!!(n.variant&&["select","radio","checkbox-group"].includes(n.type)),r={type:n.type==="checkbox"?"boolean":n.type==="date"||n.type==="icon"||n.type==="image"?"string":a?"chooser":n.type,label:n.label,required:n.required,options:n.options,formConfig:{...n.placeholder&&{placeholder:n.placeholder},...n.helper&&{helperText:n.helper},...n.tooltip&&{tooltip:n.tooltip},...l}};a&&(n.variant&&(r.variant=n.variant),(n.multiple||n.type==="checkbox-group")&&(r.multiple=!0),n.density&&(r.density=n.density),n.columns&&(r.columns=n.columns),n.accent&&(r.accent=n.accent),n.accentStyle&&(r.accentStyle=n.accentStyle),n.glow&&(r.glow=!0),n.glowColour&&(r.glowColour=n.glowColour),n.shadow&&(r.shadow=n.shadow),n.shadowColour&&(r.shadowColour=n.shadowColour)),o[n.name]=r}),o}function Bt(e,t){const o=typeof e=="string"?document.querySelector(e):e;o&&(t||[]).forEach(n=>{if(n.type!=="date"||!n.name)return;const l=o.querySelector(`[name="${n.name}"]`);l&&l.type!=="date"&&(l.type="date")})}function Ot(e,t){const o=e?.querySelector?.(".dm-plugin-banner-name .dm-plugin-banner-sep");if(o){for(;o.nextSibling;)o.nextSibling.remove();o.after(document.createTextNode(String(t||"")))}}const Nn=e=>String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");let H=null,Rt=0,Mt=!1;const Te=e=>String(e||"").split("?")[0].replace(/\/$/,"");function Ln(){Mt||!window.R?.use||(Mt=!0,R.use((e,t,o)=>!H||!at(t?.path,e?.path)||H.path!==Te(t?.path)||(H.flush(),!H.dirty())?o():E.confirm("You have unsaved changes to this form. Leave anyway? They are kept as a draft for your next visit to it.").then(n=>{n?o():history.replaceState(null,"",`#${t.path}`)})),M.subscribe("router:afterChange",({to:e,from:t}={})=>{if(H&&H.path===Te(t?.path)&&at(t?.path,e?.path)){const o=H;H=null,o.dispose()}}))}export const formEditorView={templateUrl:"/admin/js/templates/form-editor.html",async onMount(e){if(Ln(),H){const p=H;H=null,p.dispose()}const t=Te(location.hash.replace(/^#/,"")),o=++Rt,n=()=>o!==Rt||!e.get(0)?.isConnected||Te(location.hash.replace(/^#/,""))!==t;w=[],q=null,he=null;const l=window.location.hash.match(/\/forms\/edit\/([^/?#]+)/);q=l?l[1]:null;let a=null;const i=E.loader(e.get(0),{type:"dots"});if(q)try{if(a=await ie(`/forms/${q}`),n()){i.destroy();return}w=a.fields||[],he=a.actions?.collection??null}catch{if(n()){i.destroy();return}E.toast("Could not load form.",{type:"error"})}be={};try{const p=await ie("/projects").catch(()=>[]);if(n()){i.destroy();return}const v=e.find("#field-project").get(0);v&&(Array.isArray(p)?p:[]).forEach(g=>{const C=document.createElement("option");C.value=g.slug,C.textContent=g.name||g.slug,v.appendChild(C)})}catch{}if(i.destroy(),Ot(e.get(0),a?a.title||a.slug:"New form"),q||e.find("#field-title").get(0).addEventListener("input",function(){e.find("#field-slug").val(rt(this.value))}),E.tabs(e.find("#editor-tabs").get(0)),a){e.find("#field-title").val(a.title),e.find("#field-slug").val(a.slug),e.find("#field-description").val(a.description||""),e.find("#field-bundled").prop("checked",!!a.bundled),be=a.meta||{},e.find("#field-project").val(a.meta?.project||"");const p=a.settings||{};e.find("#setting-submit-text").val(p.submitText||"Submit"),e.find("#setting-success-message").val(p.successMessage||""),e.find("#setting-layout").val(p.layout||"grid"),e.find("#setting-columns").val(ke(p.columns)),e.find("#setting-submit-span").val(p.submitSpan||""),e.find("#columns-group").get(0).style.display=(p.layout||"grid")==="grid"?"":"none",e.find("#setting-honeypot").prop("checked",p.honeypot!==!1),e.find("#setting-rate-limit").val(p.rateLimitPerMinute||3),e.find("#setting-success-redirect").val(p.successRedirect||"");const v=a.actions?.email||{};e.find("#action-email-enabled").prop("checked",v.enabled||!1),e.find("#action-email-recipients").val(v.recipients||""),e.find("#action-email-subject-prefix").val(v.subjectPrefix||"");const g=a.actions?.webhook||{};e.find("#action-webhook-enabled").prop("checked",g.enabled||!1),e.find("#action-webhook-url").val(g.url||""),e.find("#action-webhook-method").val(g.method||"POST")}const r=a?.settings?.actionSlug||"";try{const p=await ie("/actions").catch(()=>[]);if(n())return;const v=e.find("#action-cms-slug").get(0);if(fe=Array.isArray(p)?p:[],(Array.isArray(p)?p:[]).forEach(g=>{const C=document.createElement("option");C.value=g.slug,C.textContent=g.title||g.slug,g.slug===r&&(C.selected=!0),v.appendChild(C)}),!p.length){const g=document.createElement("option");g.value="",g.textContent="No actions available",g.disabled=!0,v.appendChild(g)}}catch{}const c=p=>{e.find("#columns-group").get(0).style.display=p?"":"none",document.querySelectorAll(".fb-grid-row").forEach(v=>{v.style.display=p?"flex":"none"})};e.find("#setting-layout").get(0)?.addEventListener("change",function(){c(this.value==="grid")}),c(e.find("#setting-layout").val()==="grid"),Q(e);const s=e.find("#add-element-menu").get(0);e.find("#add-element-btn").get(0).addEventListener("click",p=>{p.stopPropagation(),s.style.display=s.style.display==="none"?"":"none"}),ne&&document.removeEventListener("click",ne),ne=()=>{s&&(s.style.display="none")},document.addEventListener("click",ne),e.find("#add-field-btn").get(0).addEventListener("click",()=>{s&&(s.style.display="none"),D();const p=w.length;w.push({name:`field_${p+1}`,type:"string",label:"New Field",required:!1,placeholder:""}),Q(e);const v=e.find("#fields-list").get(0)?.lastElementChild;if(v){const g=v.querySelector(".fb-field-body");g&&(g.style.display="")}}),e.find("#add-spacer-btn").get(0).addEventListener("click",()=>{s&&(s.style.display="none"),D(),w.push({type:"spacer"}),Q(e)}),e.find("#add-page-break-btn").get(0).addEventListener("click",()=>{s&&(s.style.display="none"),D();const p=w.filter(v=>v.type==="page-break").length+2;w.push({type:"page-break",label:`Step ${p}`,description:""}),Q(e)}),ge=()=>{const p=Se(e),v=e.find("#preview-container").get(0);if(!v)return;const g=e.find("#preview-test-result").get(0),C=e.find("#preview-test-badge").get(0);g&&(g.style.display="none",g.textContent=""),C&&(C.style.display=q?"":"none"),e.find("#preview-card").get(0).style.display="",v.textContent="";const z=document.createElement("div");z.id="fb-preview-form",v.appendChild(z);const U=q?async $=>{g&&(g.style.display="none",g.textContent="");try{const L=await fetch(`/api/forms/submit/${q}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify($)}),Y=await L.json();if(!L.ok)throw new Error(Y.error||"Submission failed.");g&&(g.textContent=Y.message||p.settings?.successMessage||"Submitted successfully.",g.style.cssText="display:block;margin-top:.75rem;padding:.6rem .9rem;border-radius:6px;font-size:.9rem;background:color-mix(in srgb, var(--dm-success) 12%, transparent);color:var(--dm-success);"),E.toast("Test submission stored.",{type:"success"})}catch(L){g&&(g.textContent=L.message,g.style.cssText="display:block;margin-top:.75rem;padding:.6rem .9rem;border-radius:6px;font-size:.9rem;background:color-mix(in srgb, var(--dm-danger) 12%, transparent);color:var(--dm-danger);"),E.toast(L.message,{type:"error"})}return!1}:()=>!1,ue=p.fields.some($=>$.type==="page-break");if(z.addEventListener("dm-form-end",$=>{const L=$.detail?.validation;if(L&&!L.valid){E.toast(`Still to answer: ${L.missing.map(Y=>Y.label).concat(L.errors).join(", ")}`,{type:"warning"});return}U($.detail?.values||{})}),typeof F<"u"){const $=ke(p.settings?.columns),L=p.settings?.layout||"grid";if(ue&&F.wizard){const Y=jt(p.fields,p.title),me=Y.map(te=>({title:te.title,description:te.description,fields:It(te.fields,$)}));let P=0,Z=null,Le=null;const Xe=te=>{const Ze=(Y[P]||{}).fields||[];Bt("#fb-preview-form",Ze),tt(z,Ze),te&&window.FormLogicEngine&&dt(p.fields)&&(Z?.destroy?.(),Z=new window.FormLogicEngine.FormLogicRuntime({fields:p.fields},z,{baseValues:Le?.getStepData?Le.getStepData():{}}),Z.init())};Le=F.wizard("#fb-preview-form",{schema:{steps:me},layout:L,columns:$,finishText:p.settings?.submitText||"Finish",onStepChange:te=>{P=te,queueMicrotask(()=>Xe(!0))},onSubmit:U}),Xe(!1)}else if(F.render){const Y=It(p.fields,$),me={};if(p.fields.forEach(P=>{if(!(!P.name||P.type==="page-break"||P.type==="spacer")&&(P.type==="select"||P.type==="multiselect")&&P.required){const Z=(P.options||[])[0];Z&&(me[P.name]=typeof Z=="object"?Z.value:Z)}}),F.render("#fb-preview-form",Y,me,{submitText:p.settings?.submitText||"Submit",layout:L,columns:$,onSubmit:U}),L==="grid"&&p.settings?.submitSpan==="full"){const P=document.querySelector("#fb-preview-form .form-buttons");P&&P.classList.add("col-span-full")}Bt("#fb-preview-form",p.fields),tt(z,p.fields)}window.FormLogicEngine&&dt(p.fields)&&requestAnimationFrame(()=>{new window.FormLogicEngine.FormLogicRuntime({fields:p.fields},z).init()})}else{const $=document.createElement("p");$.textContent=`${p.fields.filter(L=>L.type!=="page-break").length} field(s): ${p.fields.filter(L=>L.type!=="page-break").map(L=>L.label).join(", ")}`,$.style.cssText="color:var(--dm-text-muted);font-style:italic;",z.appendChild($)}},ge();const d=e.find("#editor-tabs").get(0),u=e.find("#preview-card").get(0),b=p=>{u&&u.contains(p.target)||Oe()};d&&(d.addEventListener("input",b),d.addEventListener("change",b));const y=e.get(0),f=[],h=p=>(f.push(p),p);let k=!1;const m=M.observable(!1),x=M.observable(!1);let N=q,T=Se(e),I=Ae(T),B=T;const G=()=>{k||!y.isConnected||(B=Se(e),m.value=Ae(B)!==I)};let O=null;Be=()=>{clearTimeout(O),O=setTimeout(G,150)},h(()=>{clearTimeout(O),Be=null,ce=null});const X=p=>{const v=p.settings||{};e.find("#field-title").val(p.title||""),e.find("#field-slug").val(p.slug||""),e.find("#field-description").val(p.description||""),e.find("#field-bundled").prop("checked",!!p.bundled),be={...p.meta||{}},e.find("#field-project").val(p.meta?.project||""),e.find("#setting-submit-text").val(v.submitText||"Submit"),e.find("#setting-success-message").val(v.successMessage||""),e.find("#setting-layout").val(v.layout||"grid"),e.find("#setting-columns").val(ke(v.columns)),e.find("#setting-submit-span").val(v.submitSpan||""),e.find("#setting-honeypot").prop("checked",v.honeypot!==!1),e.find("#setting-rate-limit").val(v.rateLimitPerMinute||3),e.find("#setting-success-redirect").val(v.successRedirect||""),e.find("#action-cms-slug").val(v.actionSlug||"");const g=p.actions?.email||{};e.find("#action-email-enabled").prop("checked",!!g.enabled),e.find("#action-email-recipients").val(g.recipients||""),e.find("#action-email-subject-prefix").val(g.subjectPrefix||"");const C=p.actions?.webhook||{};e.find("#action-webhook-enabled").prop("checked",!!C.enabled),e.find("#action-webhook-url").val(C.url||""),e.find("#action-webhook-method").val(C.method||"POST"),w=JSON.parse(JSON.stringify(p.fields||[])),c((v.layout||"grid")==="grid"),Q(e)};async function W(){if(x.peek()||k)return;const p=Se(e);if(!p.title){E.toast("Please enter a form title (Form Details).",{type:"error"});return}const v=dn(p.fields);v.length&&E.toast(`Some trigger messages use ${v.map(C=>`{{${C}}}`).join(", ")}, which will show as blank: no field has that name, or it is a password, file or image.`,{type:"warning",duration:9e3}),x.value=!0;const g=q;try{q?(await ie(`/forms/${q}`,{method:"PUT",body:JSON.stringify(p)}),E.toast("Form saved.",{type:"success"})):(q=(await ie("/forms",{method:"POST",body:JSON.stringify(p)})).slug,E.toast("Form created.",{type:"success"})),N=q,T=p,I=Ae(p),B=p,m.value=!1;for(const C of new Set([se(g),se(q)]))try{S.remove?S.remove(C):S.set(C,null)}catch{}Ot(y,p.title),Gt("#/forms"),g||R.navigate(`/forms/edit/${q}`)}catch(C){g||(q=null),E.toast(C.message||"Failed to save form.",{type:"error"})}finally{x.value=!1}}async function zt(){m.peek()&&(!await E.confirm("Discard the changes you have not saved?")||k||(X(T),G(),Ke()))}const Ve=y.querySelector(".fex-bar");if(Ve){const p=M.applyBindings({dirty:m,saving:x},Ve,{methods:{save:W,discard:zt}});h(()=>p.dispose())}h(Vt(y,{position:"bottom"})),qe(e,{id:"form-editor-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>W()}),qe(e,{id:"form-editor-back",icon:"arrow-left",title:"All forms",onClick:()=>{location.hash="#/forms"}}),q&&qe(e,{id:"form-editor-subs",icon:"list",title:"Submissions",onClick:()=>{location.hash=`#/forms/${encodeURIComponent(q)}/submissions`}});const Ge=p=>{n()||!(p.ctrlKey||p.metaKey)||String(p.key).toLowerCase()!=="s"||(p.preventDefault(),p.stopImmediatePropagation(),W())};document.addEventListener("keydown",Ge,!0),h(()=>document.removeEventListener("keydown",Ge,!0));const K=e.find("#fields-list").get(0),Dt=p=>p?.closest?.(".fb-field-card"),He=p=>Number(p?.dataset.index),We=p=>p?.querySelector(":scope > .fb-field-body"),Ue=p=>{const v=We(p);v&&(v.style.display=v.style.display==="none"?"":"none")},oe=(p,v)=>{w=p,Q(e),v!=null&&K?.querySelectorAll(".fb-field-card")[v]?.focus()},_t=async p=>{try{await navigator.clipboard.writeText(p),E.toast(`Copied "${p}".`,{type:"success"})}catch{E.toast(`Copy failed - the name is ${p}.`,{type:"info"})}},Pt=p=>{const v=Dt(p),g=He(v);D();const C=w[g];if(!C)return[];const z=C.type!=="spacer"&&C.type!=="page-break",U=We(v),ue=U&&U.style.display!=="none";return[{header:$e(C)},...U?[{label:ue?"Close":"Edit",icon:ue?"chevron-up":"edit",action:()=>Ue(v)}]:[],...z?[{label:"Logic & triggers",icon:"zap",action:()=>{U&&(U.style.display=""),(U?.querySelector(".fb-field-logic")||U)?.scrollIntoView({block:"center",behavior:"smooth"})}}]:[],{label:"Duplicate",icon:"copy",action:()=>{D(),oe(Wt(w,g),g+1)}},{type:"divider"},{label:"Move up",icon:"arrow-up",shortcut:"Alt+\u2191",disabled:g===0,action:()=>{D(),oe(ot(w,g,-1),g-1)}},{label:"Move down",icon:"arrow-down",shortcut:"Alt+\u2193",disabled:g>=w.length-1,action:()=>{D(),oe(ot(w,g,1),g+1)}},...z&&C.name?[{label:"Copy field name",icon:"clipboard",action:()=>_t(C.name)}]:[],{type:"divider"},{label:"Delete\u2026",icon:"trash",danger:!0,action:async()=>{if(C.type!=="spacer"&&!await E.confirm(`Remove "${Nn($e(C))}"?`))return;D();const $=[...w];$.splice(g,1),oe($,Math.min(g,$.length-1))}}]};if(K&&typeof E.sortable=="function"){const p=E.sortable(K,{items:".fb-field-card",handle:".fb-drag-grip",key:null,onSort:({from:v,to:g})=>{D();const C=[...w],[z]=C.splice(v,1);C.splice(g,0,z),oe(C,g)}});h(()=>p.destroy())}let Je=null;if(K&&E.contextMenu&&(Je=E.contextMenu(K,{cursor:"glyph",match:".fb-field-card > :first-child",inherit:!1,density:"compact",items:p=>Pt(p)}),h(()=>Je?.destroy?.())),ce=p=>{for(const v of p.querySelectorAll(".fb-field-card")){v.tabIndex=0;const g=v.firstElementChild;if(!g||g.querySelector(".fex-kebab"))continue;const C=document.createElement("button");C.type="button",C.className="fex-kebab",C.textContent="\u22EE",C.title="Actions",D(),C.setAttribute("aria-label",`Actions for ${$e(w[He(v)])}`),C.addEventListener("click",z=>{z.stopPropagation(),nt(C)}),g.appendChild(C)}},K){ce(K);const p=v=>{const g=v.target?.classList?.contains("fb-field-card")?v.target:null;g&&(v.key==="F10"&&v.shiftKey?(v.preventDefault(),nt(g.firstElementChild)):v.key==="Enter"&&(v.preventDefault(),Ue(g)))};K.addEventListener("keydown",p),h(()=>K.removeEventListener("keydown",p))}const Ke=()=>{try{const p=se(N);S.remove?S.remove(p):S.set(p,null)}catch{}},Ye=p=>{if(!m.peek()||!p){Ke();return}try{S.set(se(N),{data:Ht(p),base:lt(I),at:new Date().toISOString()})}catch{}},Qe=p=>{G(),m.peek()&&(Ye(B),p.preventDefault(),p.returnValue="")};window.addEventListener("beforeunload",Qe),h(()=>window.removeEventListener("beforeunload",Qe)),h(()=>{ne&&(document.removeEventListener("click",ne),ne=null)}),H={path:t,dirty:()=>m.peek(),flush:()=>{clearTimeout(O),G()},dispose(){k||(Ye(B),k=!0,f.splice(0).reverse().forEach(p=>{try{p?.()}catch{}}))}};const Ne=se(q);let ee=null;try{ee=S.get(Ne)}catch{}if(ee?.data){try{S.remove?S.remove(Ne):S.set(Ne,null)}catch{}const p=ee.base&&ee.base!==lt(I),v=ee.at?` (${Jt(ee.at)})`:"";await E.confirm(`You left unsaved changes to this form${v}.${p?" The saved form has changed since then.":""} Pick up where you left off?`)&&!k&&(X(Ut(ee.data,T)),G())}}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "0.98.0",
3
+ "version": "0.99.0",
4
4
  "description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
5
5
  "type": "module",
6
6
  "main": "server/server.js",
@@ -83,7 +83,7 @@
83
83
  "@fastify/rate-limit": "^10.3.0",
84
84
  "@fastify/static": "9.1.1",
85
85
  "bcryptjs": "^3.0.3",
86
- "domma-js": "0.45.0",
86
+ "domma-js": "0.46.1",
87
87
  "dotenv": "^17.2.3",
88
88
  "fastify": "5.8.5",
89
89
  "gray-matter": "^4.0.3",
@@ -1 +1 @@
1
- (function(l,d){typeof module<"u"&&module.exports?module.exports=d():l.FormLogicEngine=d()})(typeof globalThis<"u"?globalThis:this,function(){"use strict";function l(e){return e===""||e===null||e===void 0?"":String(e)}function d(e){const r=parseFloat(e);return isNaN(r)?null:r}function p(e){return e==null?!0:typeof e=="string"?e.trim()==="":Array.isArray(e)?e.length===0:!1}const $={not_equals:"equals",not_contains:"contains",not_in:"in"};function q(e,r){const t=l(r),n=l(e.value);switch(e.operator){case"equals":return t===n;case"not_equals":return t!==n;case"contains":return t.toLowerCase().includes(n.toLowerCase());case"not_contains":return!t.toLowerCase().includes(n.toLowerCase());case"starts_with":return t.toLowerCase().startsWith(n.toLowerCase());case"ends_with":return t.toLowerCase().endsWith(n.toLowerCase());case"greater_than":{const i=d(r),s=d(e.value);return i!==null&&s!==null&&i>s}case"less_than":{const i=d(r),s=d(e.value);return i!==null&&s!==null&&i<s}case"is_empty":return p(r);case"is_not_empty":return!p(r);case"in":return(Array.isArray(e.value)?e.value.map(l):String(e.value).split(",").map(s=>s.trim())).includes(t);case"not_in":return!(Array.isArray(e.value)?e.value.map(l):String(e.value).split(",").map(s=>s.trim())).includes(t);case"matches_regex":try{const i=e.flags||"";return new RegExp(e.value,i).test(t)}catch(i){return typeof console<"u"&&console.warn&&console.warn('[FormLogicEngine] Invalid regex in condition for field "'+e.field+'": '+e.value,i&&i.message),!1}default:return!0}}function y(e,r){if(!e||!e.field||!e.operator)return!0;const t=r[e.field];if(!Array.isArray(t))return q(e,t);if(e.operator==="is_empty")return p(t);if(e.operator==="is_not_empty")return!p(t);const n=$[e.operator];if(n){const i=Object.assign({},e,{operator:n});return!t.some(s=>q(i,s))}return t.some(i=>q(e,i))}function h(e,r){return e?Array.isArray(e.all)?e.all.every(t=>t.all||t.any?h(t,r):y(t,r)):Array.isArray(e.any)?e.any.some(t=>t.all||t.any?h(t,r):y(t,r)):e.field&&e.operator?y(e,r):!0:!0}function A(e,r){const t=e.logic;if(!t||!t.visibility)return"visible";const n=t.visibility,i=n.conditions||[];for(const s of i)if(s.when&&h(s.when,r))return s.then==="hidden"?"hidden":"visible";return n.default==="hidden"?"hidden":"visible"}function _(e,r){const t=e.logic;if(!t||!t.requirement)return e.required||!1;const n=t.requirement,i=n.conditions||[];for(const s of i)if(s.when&&h(s.when,r))return s.then===!0;return n.default===!0}function v(e){if(e==null||e==="")return null;const r=Number(e);return Number.isFinite(r)?r:null}function V(e,r){if(p(r)||Array.isArray(r)||typeof r=="object")return[];const t=e.label||e.name;if(e.type==="number"){const a=Number(r);if(!Number.isFinite(a)||String(r).trim()==="")return[{message:`${t} must be a number.`}];const o=v(e.min),c=v(e.max);return o!==null&&a<o?[{message:`${t} must be at least ${o}.`}]:c!==null&&a>c?[{message:`${t} must be no more than ${c}.`}]:[]}const n=String(r).length,i=v(e.minLength),s=v(e.maxLength);return i!==null&&n<i?[{message:`${t} must be at least ${i} characters.`}]:s!==null&&n>s?[{message:`${t} must be no more than ${s} characters.`}]:[]}function x(e,r,t){const n=V(e,r),i=e.logic;if(!i||!Array.isArray(i.validation))return n;for(const s of i.validation)switch(s.type){case"regex":{if(!p(r))try{new RegExp(s.pattern||"",s.flags||"").test(l(r))||n.push({message:s.message||"Invalid format."})}catch{}break}case"match":{const a=t[s.field];!p(r)&&l(r)!==l(a)&&n.push({message:s.message||"Fields do not match."});break}}return n}function C(e,r){const t=e.logic;if(!t||!t.cascade||!t.cascade.sourceField)return null;const n=t.cascade,i=n.mapping||{},s=r[n.sourceField];if(Array.isArray(s)){const o=new Set,c=[];return s.forEach(function(g){const f=l(g);Object.prototype.hasOwnProperty.call(i,f)&&(i[f]||[]).forEach(function(S){const O=l(S&&S.value);o.has(O)||(o.add(O),c.push(S))})}),c.length?c:n.defaultOptions||null}const a=l(s);return Object.prototype.hasOwnProperty.call(i,a)?i[a]||[]:n.defaultOptions||null}const W={fade:250,slide:300,scale:200};function T(e,r){const t=Number(r);return!isFinite(t)||t<0||t>5e3?W[e]||250:Math.round(t)}function D(e,r,t){const n=T(r,t);if(r==="fade")e.style.transition="opacity "+n+"ms ease",e.style.opacity="1",requestAnimationFrame(function(){e.style.opacity="0",setTimeout(function(){e.classList.add("fb-field-hidden"),e.style.transition="",e.style.opacity=""},n+10)});else if(r==="slide"){const i=Math.round(n*.67);e.style.overflow="hidden",e.style.maxHeight=e.scrollHeight+"px",e.style.transition="max-height "+n+"ms ease, opacity "+i+"ms ease",requestAnimationFrame(function(){e.style.maxHeight="0",e.style.opacity="0",setTimeout(function(){e.classList.add("fb-field-hidden"),e.style.transition="",e.style.maxHeight="",e.style.opacity="",e.style.overflow=""},n+20)})}else r==="scale"&&(e.style.transition="opacity "+n+"ms ease, transform "+n+"ms ease",e.style.transform="scale(1)",e.style.opacity="1",requestAnimationFrame(function(){e.style.transform="scale(0.95)",e.style.opacity="0",setTimeout(function(){e.classList.add("fb-field-hidden"),e.style.transition="",e.style.transform="",e.style.opacity=""},n+20)}))}function H(e,r,t){const n=T(r,t);if(e.classList.remove("fb-field-hidden"),r==="fade")e.style.opacity="0",e.style.transition="opacity "+n+"ms ease",requestAnimationFrame(function(){e.style.opacity="1",setTimeout(function(){e.style.transition="",e.style.opacity=""},n+10)});else if(r==="slide"){const i=Math.round(n*.67);e.style.overflow="hidden",e.style.maxHeight="0",e.style.opacity="0",e.style.transition="max-height "+n+"ms ease, opacity "+i+"ms ease",requestAnimationFrame(function(){e.style.maxHeight=e.scrollHeight+"px",e.style.opacity="1",setTimeout(function(){e.style.transition="",e.style.maxHeight="",e.style.opacity="",e.style.overflow=""},n+20)})}else r==="scale"&&(e.style.transform="scale(0.95)",e.style.opacity="0",e.style.transition="opacity "+n+"ms ease, transform "+n+"ms ease",requestAnimationFrame(function(){e.style.transform="scale(1)",e.style.opacity="1",setTimeout(function(){e.style.transition="",e.style.transform="",e.style.opacity=""},n+20)}))}const N={banner:"state","disable-fields":"state","hide-fields":"state","require-fields":"state","block-submit":"state","end-form":"state",toast:"event",celebrate:"event","goto-step":"event",redirect:"event","run-action":"event",notify:"event"},P=["block-submit","hide-fields","disable-fields","require-fields","end-form","redirect","run-action","notify"];function k(e){return!!e&&!!e.name&&e.type!=="page-break"&&e.type!=="spacer"}function b(e,r,t){const n=(t||[]).filter(k),i=e.target||(e.fields&&e.fields.length?"listed":"rest");if(i==="listed"){const a=new Set(e.fields||[]);return n.filter(o=>a.has(o.name)&&o.name!==r.name).map(o=>o.name)}if(i==="all")return n.filter(a=>a.name!==r.name).map(a=>a.name);const s=n.findIndex(a=>a.name===r.name);return s===-1?[]:n.slice(s+1).map(a=>a.name)}function w(e,r){for(const t of r)e.includes(t)||e.push(t)}function z(e,r,t,n,i){if(!r||!r.type)return;const s=N[r.type];if(s){if(s==="event"){e.events.push({key:i,type:r.type,field:t.name,action:r});return}switch(r.type){case"banner":e.banners.push({key:i,field:t.name,message:r.message||"",level:r.level||"info",position:r.position||"field-below",dismissible:r.dismissible!==!1});break;case"hide-fields":w(e.hidden,b(r,t,n));break;case"disable-fields":w(e.disabled,b(r,t,n));break;case"require-fields":w(e.required,b(r,t,n));break;case"block-submit":e.blocks.push({key:i,message:r.message||"This form cannot be submitted yet."});break;case"end-form":w(e.masked,b({target:"rest"},t,n)),e.ends.push({key:i,field:t.name,message:r.message||"",finishLabel:r.finishLabel||"Finish",doneMessage:r.doneMessage||"",record:r.record!==!1,finish:r.finish!==!1}),r.finish===!1&&e.blocks.push({key:i,quiet:!0,message:r.message||"This form cannot be submitted."});break;default:break}}}function L(e,r){const t={active:[],banners:[],hidden:[],disabled:[],required:[],blocks:[],events:[],masked:[],ends:[]};return(e||[]).forEach(n=>{!k(n)||!Array.isArray(n.triggers)||n.triggers.forEach((i,s)=>{if(!i||i.enabled===!1)return;const a=i.id||`${n.name}::${s}`,o=h(i.when,r),c=`${a}:${o?"then":"else"}`,g=o?i.then:i.otherwise;!Array.isArray(g)||!g.length||(t.active.push(c),g.forEach(f=>z(t,f,n,e,c)))})}),t.required=t.required.filter(n=>!t.hidden.includes(n)&&!t.masked.includes(n)),t}function F(e){return(e||[]).some(r=>Array.isArray(r&&r.triggers)&&r.triggers.length>0)}function u(e,r,t){this._form=e,this._wrapper=r,this._fields=(e.fields||[]).filter(n=>n.type!=="page-break"),this._listeners=[],this._depMap=new Map,this._initialEval=!1,this._baseValues=t&&t.baseValues||null,this._hasTriggers=F(this._fields),this._triggerState=null,this._triggerPrev=new Set,this._triggerFired=new Set,this._triggerApplied={hidden:[],disabled:[],required:[],masked:[]},this._banners=new Map,this._dismissed=new Set,this._end=null}function m(e,r,t){const n=t||"",i=r||"";return n+'[name="'+e+'"]'+i+", "+n+'[name="'+e+'[]"]'+i}function G(e){return String(e||"").replace(/\[\]$/,"")}u.prototype._fieldInputs=function(e){return this._wrapper.querySelectorAll(m(e))},u.prototype._fieldInput=function(e){return this._wrapper.querySelector(m(e))},u.prototype._buildDepMap=function(){this._depMap.clear();const e=this;e._fields.forEach(function(r){const t=r.logic;if(!t)return;function n(i){if(!i)return;(i.all||i.any||[]).forEach(function(a){a.field?(e._depMap.has(a.field)||e._depMap.set(a.field,new Set),e._depMap.get(a.field).add(r.name)):n(a)})}if(t.visibility&&(t.visibility.conditions||[]).forEach(function(i){n(i.when)}),t.requirement&&(t.requirement.conditions||[]).forEach(function(i){n(i.when)}),t.cascade&&t.cascade.sourceField){const i=t.cascade.sourceField;e._depMap.has(i)||e._depMap.set(i,new Set),e._depMap.get(i).add(r.name)}})};function I(e,r){const t={};return(r||[]).forEach(function(n){if(!n||!n.name)return;const i=e.querySelectorAll(m(n.name));if(!i.length)return;const s=i[0];if(s.type==="radio"){const a=e.querySelector(m(n.name,":checked"));t[n.name]=a?a.value:""}else if(s.type==="checkbox")if(i.length>1||/\[\]$/.test(s.name||"")){const a=[];i.forEach(function(o){o.checked&&a.push(o.value)}),t[n.name]=a}else t[n.name]=s.checked?s.value||"true":"";else t[n.name]=s.value}),t}function M(e,r){const t=I(e,r),n=[],i=[];return(r||[]).forEach(function(s){if(!s||!s.name||A(s,t)==="hidden")return;const a=t[s.name],o=Array.isArray(a)?a.length===0:!String(a??"").trim();if(_(s,t)&&o){n.push({name:s.name,label:s.label||s.name});return}if(o)return;const c=x(s,a,t);c.length&&i.push(c[0].message)}),{valid:n.length===0&&i.length===0,missing:n,errors:i,values:t}}u.prototype._getValues=function(){const e=this._baseValues?Object.assign({},this._baseValues):{};return this._fields.forEach(function(r){const t=this._fieldInputs(r.name);if(!t.length)return;const n=t[0];if(n.type==="radio"){const i=this._wrapper.querySelector(m(r.name,":checked"));e[r.name]=i?i.value:""}else if(n.type==="checkbox")if(t.length>1||/\[\]$/.test(n.name||"")){const i=[];t.forEach(function(s){s.checked&&i.push(s.value)}),e[r.name]=i}else e[r.name]=n.checked?n.value||"true":"";else e[r.name]=n.value},this),e},u.prototype._applyVisibility=function(e,r){const n=A(e,r)==="hidden",i=this._findFieldWrapper(e.name);if(!i)return;i.setAttribute("aria-hidden",String(n));const s=this._initialEval?"none":e.logic?.visibility?.transition||"none";if(s==="none"){i.classList.toggle("fb-field-hidden",n);return}const a=e.logic?.visibility?.duration;i.style.transition="",n?D(i,s,a):H(i,s,a)},u.prototype._applyRequirement=function(e,r){const t=_(e,r),n=this._fieldInput(e.name);if(!n)return;if(/\[\]$/.test(n.name||"")||n.closest&&n.closest(".domma-chooser")){const s=this._findFieldWrapper(e.name);n.removeAttribute("required"),s&&(s.setAttribute("aria-required",String(t)),s.classList.toggle("fb-field-required",t));return}t?n.setAttribute("required",""):n.removeAttribute("required");const i=this._findFieldWrapper(e.name);i&&(i.setAttribute("aria-required",String(t)),i.classList.toggle("fb-field-required",t))},u.prototype._applyCascade=function(e,r){const t=C(e,r);if(t===null)return;const n=this._wrapper.querySelector(m(e.name,"","select"));if(!n)return;const i=n.value;n.textContent="",t.forEach(function(s){const a=document.createElement("option");a.value=s.value,a.textContent=s.label||s.value,s.value===i&&(a.selected=!0),n.appendChild(a)})},u.prototype._applyValidation=function(e,r){const t=this._fieldInput(e.name);if(!t)return;const n=x(e,r[e.name],r);let i=t.parentNode.querySelector(".fb-validation-error");n.length?(i||(i=document.createElement("div"),i.className="fb-validation-error",t.parentNode.appendChild(i)),i.textContent=n[0].message):i&&i.remove()},u.prototype._findFieldWrapper=function(e){const r=this._fieldInput(e);if(!r)return null;const t=r.closest("form")||this._wrapper;let n=r;for(;n.parentNode&&n.parentNode!==t;)n=n.parentNode;return n},u.prototype.evaluate=function(e){const r=this._getValues(),t=this;let n;if(e&&this._depMap.has(e)){const i=this._depMap.get(e);n=this._fields.filter(function(s){return i.has(s.name)})}else n=this._fields;n.forEach(function(i){i.logic&&(t._applyVisibility(i,r),t._applyRequirement(i,r),t._applyCascade(i,r))}),this._applyTriggers(r)},u.prototype.init=function(){this._buildDepMap();const e=this;if(this._wrapper.querySelectorAll("input, select, textarea").forEach(function(n){const i=G(n.getAttribute("name"));if(!i)return;const s=function(){e.evaluate(i)};n.addEventListener("input",s),n.addEventListener("change",s),e._listeners.push({el:n,event:"input",fn:s}),e._listeners.push({el:n,event:"change",fn:s});const a=e._fields.find(function(o){return o.name===i});if(a&&a.logic&&Array.isArray(a.logic.validation)&&a.logic.validation.length){const o=function(){const c=e._getValues();e._applyValidation(a,c)};n.addEventListener("blur",o),e._listeners.push({el:n,event:"blur",fn:o})}}),this._wrapper.querySelectorAll("[data-chooser-field]").forEach(function(n){const i=n.getAttribute("data-chooser-field");if(!i)return;const s=function(){setTimeout(function(){e.evaluate(i)},0)};n.addEventListener("click",s),e._listeners.push({el:n,event:"click",fn:s})}),this._hasTriggers){const n=function(i){if(e._end&&!(i.type==="click"&&!(i.target&&i.target.closest&&i.target.closest(j)))){if(!e._end.spec.finish){i.preventDefault(),i.stopImmediatePropagation();return}e._wrapper.dataset.triggerBlocked!=="true"&&(i.preventDefault(),i.stopImmediatePropagation(),e._finishEnded())}};this._wrapper.addEventListener("click",n,!0),this._wrapper.addEventListener("submit",n,!0),this._listeners.push({el:this._wrapper,event:"click",fn:n,capture:!0}),this._listeners.push({el:this._wrapper,event:"submit",fn:n,capture:!0})}this._initialEval=!0,this.evaluate(),this._initialEval=!1},u.prototype.destroy=function(){this._listeners.forEach(function(e){e.el.removeEventListener(e.event,e.fn,!!e.capture)}),this._listeners=[],this._banners.forEach(function(e){e&&e.parentNode&&e.parentNode.removeChild(e)}),this._banners.clear(),this._syncEnd([])},u.prototype._applyTriggers=function(e){if(!this._hasTriggers)return;const r=L(this._fields,e);this._triggerState=r;const t=this._triggerApplied||{hidden:[],disabled:[],required:[]};t.hidden.forEach(n=>{r.hidden.indexOf(n)===-1&&this._setTriggerHidden(n,!1)}),r.hidden.forEach(n=>this._setTriggerHidden(n,!0)),t.disabled.forEach(n=>{r.disabled.indexOf(n)===-1&&this._setTriggerDisabled(n,!1)}),r.disabled.forEach(n=>this._setTriggerDisabled(n,!0)),t.required.forEach(n=>{r.required.indexOf(n)===-1&&this._restoreRequired(n,e)}),r.required.forEach(n=>this._setTriggerRequired(n,!0)),(t.masked||[]).forEach(n=>{r.masked.indexOf(n)===-1&&this._setTriggerMasked(n,!1)}),r.masked.forEach(n=>this._setTriggerMasked(n,!0)),this._triggerApplied={hidden:r.hidden.slice(),disabled:r.disabled.slice(),required:r.required.slice(),masked:r.masked.slice()},this._syncBanners(r.banners),this._syncSubmitBlock(r.blocks),this._syncEnd(r.ends),this._fireTriggerEvents(r)},u.prototype._setTriggerHidden=function(e,r){const t=this._findFieldWrapper(e);t&&(t.classList.toggle("fb-field-trigger-hidden",r),t.setAttribute("aria-hidden",String(r||t.classList.contains("fb-field-hidden"))))},u.prototype._setTriggerMasked=function(e,r){const t=this._findFieldWrapper(e);t&&(t.classList.toggle("fb-field-trigger-masked",r),t.setAttribute("aria-hidden",String(r||t.classList.contains("fb-field-hidden")||t.classList.contains("fb-field-trigger-hidden"))))},u.prototype._setTriggerDisabled=function(e,r){const t=this._findFieldWrapper(e);t&&t.classList.toggle("fb-field-disabled",r),this._fieldInputs(e).forEach(function(i){i.disabled=r});const n=this._wrapper.querySelector('[data-chooser-field="'+e+'"]');n&&(n.classList.toggle("fb-field-disabled",r),n.querySelectorAll('button, [role="radio"], [role="checkbox"]').forEach(function(i){r?i.setAttribute("aria-disabled","true"):i.removeAttribute("aria-disabled")}))},u.prototype._setTriggerRequired=function(e,r){const t=this._fieldInput(e),n=this._findFieldWrapper(e);if(n&&(n.setAttribute("aria-required",String(r)),n.classList.toggle("fb-field-required",r)),!!t){if(/\[\]$/.test(t.name||"")||t.closest&&t.closest(".domma-chooser")){t.removeAttribute("required");return}r?t.setAttribute("required",""):t.removeAttribute("required")}},u.prototype._restoreRequired=function(e,r){const t=this._fields.find(function(n){return n.name===e});t&&this._setTriggerRequired(e,_(t,r))},u.prototype._syncBanners=function(e){const r=new Map;e.forEach(function(t){r.set(t.key,t)}),this._banners.forEach(function(t,n){r.has(n)||(t.parentNode&&t.parentNode.removeChild(t),this._banners.delete(n))},this),this._dismissed.forEach(function(t){r.has(t)||this._dismissed.delete(t)},this),r.forEach(function(t,n){if(this._banners.has(n)){const a=this._banners.get(n).querySelector(".fb-trigger-banner-text");a&&a.textContent!==t.message&&(a.textContent=t.message);return}if(this._dismissed.has(n))return;const i=this._buildBanner(t,n);i&&this._banners.set(n,i)},this)},u.prototype._buildBanner=function(e,r){if(typeof document>"u"||!e.message)return null;const t=document.createElement("div");t.className="fb-trigger-banner fb-trigger-banner--"+(e.level||"info"),t.setAttribute("role",e.level==="error"?"alert":"status"),t.dataset.triggerKey=r;const n=document.createElement("span");if(n.className="fb-trigger-banner-text",n.textContent=e.message,t.appendChild(n),e.dismissible){const a=document.createElement("button");a.type="button",a.className="fb-trigger-banner-close",a.setAttribute("aria-label","Dismiss"),a.textContent="\xD7";const o=this;a.addEventListener("click",function(){o._dismissed.add(r),t.parentNode&&t.parentNode.removeChild(t),o._banners.delete(r)}),t.appendChild(a)}const i=this._findFieldWrapper(e.field),s=this._wrapper.querySelector("form")||this._wrapper;return e.position==="form-top"?s.insertBefore(t,s.firstChild):i&&i.parentNode?e.position==="field-above"?i.parentNode.insertBefore(t,i):i.parentNode.insertBefore(t,i.nextSibling):s.appendChild(t),t},u.prototype._syncSubmitBlock=function(e){const r=this._wrapper.querySelector("form")||this._wrapper,t=r.querySelector('button[type="submit"], input[type="submit"], .fb-form-submit'),n=e.length>0;this._wrapper.dataset.triggerBlocked=n?"true":"false",t&&(t.disabled=n,t.setAttribute("aria-disabled",String(n)));let i=r.querySelector(".fb-trigger-block-note");const s=e.filter(function(a){return!a.quiet}).map(function(a){return a.message});if(!s.length){i&&i.parentNode&&i.parentNode.removeChild(i);return}i||(i=document.createElement("p"),i.className="fb-trigger-block-note",i.setAttribute("role","status"),(t&&t.parentNode?t.parentNode:r).appendChild(i)),i.textContent=s.join(" ")};const j='button[type="submit"], input[type="submit"], .fb-form-submit, .wizard-next, .wizard-submit';u.prototype._primaryButton=function(){return this._wrapper.querySelector(".wizard-next, .wizard-submit")||this._wrapper.querySelector('button[type="submit"], input[type="submit"], .fb-form-submit')};function R(e){return!!(e.matches&&e.matches(".wizard-next, .wizard-submit"))}function B(e,r){e.tagName==="INPUT"?e.value=r:e.textContent=r}return u.prototype._syncEnd=function(e){const r=e&&e.length?e[0]:null,t=this._end;if(t&&(!r||t.spec.key!==r.key)&&(t.panel&&t.panel.parentNode&&t.panel.parentNode.removeChild(t.panel),t.button&&t.button.isConnected&&(t.spec.finish?B(t.button,t.label):R(t.button)&&(t.button.disabled=!1)),this._end=null),this._wrapper.dataset.triggerEnded=r?"true":"false",!r||typeof document>"u")return;if(this._end){const a=this._end.panel&&this._end.panel.querySelector(".fb-trigger-end-text");a&&a.textContent!==r.message&&(a.textContent=r.message),this._end.spec=r;return}let n=null;if(r.message){n=document.createElement("div"),n.className="fb-trigger-end",n.setAttribute("role","status"),n.dataset.triggerKey=r.key;const a=document.createElement("div");a.className="fb-trigger-end-text",a.textContent=r.message,n.appendChild(a);const o=this._findFieldWrapper(r.field);o&&o.parentNode?o.parentNode.insertBefore(n,o.nextSibling):(this._wrapper.querySelector("form")||this._wrapper).appendChild(n)}const i=this._primaryButton();let s="";i&&r.finish?(s=i.tagName==="INPUT"?i.value:i.textContent,B(i,r.finishLabel)):i&&R(i)&&(i.disabled=!0),this._end={spec:r,panel:n,button:i,label:s}},u.prototype._finishEnded=function(){const e=this._triggerState||{},r=new Set([].concat(e.masked||[],e.hidden||[],e.disabled||[])),t=this,n=this._fields.filter(function(a){return k(a)&&!r.has(a.name)&&t._fieldInput(a.name)}),i=this._getValues();(e.masked||[]).forEach(function(a){delete i[a]});const s=M(this._wrapper,n);(e.required||[]).forEach(function(a){const o=n.find(function(f){return f.name===a});if(!o)return;const c=i[a];(Array.isArray(c)?c.length===0:!String(c??"").trim())&&!s.missing.some(function(f){return f.name===a})&&(s.missing.push({name:a,label:o.label||a}),s.valid=!1)}),this._wrapper.dispatchEvent(new CustomEvent("dm-form-end",{bubbles:!0,detail:{values:i,validation:s,end:this._end?this._end.spec:null}}))},u.prototype._fireTriggerEvents=function(e){const r=new Set(e.active),t=this;e.events.forEach(function(n){t._triggerPrev.has(n.key)||n.action.once&&t._triggerFired.has(n.key)||t._initialEval&&n.action.onLoad!==!0||(t._triggerFired.add(n.key),t._runTriggerEvent(n))}),this._triggerPrev=r},u.prototype._runTriggerEvent=function(e){const r=e.action;switch(r.type){case"toast":typeof E<"u"&&E&&typeof E.toast=="function"&&E.toast(r.message||"",{type:r.level||"info",duration:Number(r.duration)||4e3});break;case"celebrate":this._celebrate(r);break;default:break}typeof CustomEvent<"u"&&this._wrapper.dispatchEvent&&this._wrapper.dispatchEvent(new CustomEvent("dm-form-trigger",{bubbles:!0,detail:{type:r.type,field:e.field,action:r}}))},u.prototype._celebrate=function(e){typeof window>"u"||typeof document>"u"||window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches||import("/public/js/celebrations/index.js").then(function(r){const t=r.CelebrationsEffect;let n=e.theme||"auto";if(n==="auto"&&(n=t.getCurrentTheme(),!n))return;const i=new t({theme:n,intensity:e.intensity||"medium",enabled:!0,zIndex:999});i.init().then(function(){i.start()})}).catch(function(){})},u.prototype.submitBlock=function(){const e=this._triggerState&&this._triggerState.blocks||[];return{blocked:e.length>0,reasons:e.map(function(r){return r.message})}},{evaluateCondition:y,evaluateConditionGroup:h,evaluateFieldVisibility:A,evaluateFieldRequirement:_,validateField:x,collectValues:I,validateFields:M,getCascadeOptions:C,resolveTriggerState:L,resolveTriggerTargets:b,hasTriggers:F,TRIGGER_ACTION_KINDS:N,SERVER_TRIGGER_ACTIONS:P,FormLogicRuntime:u}});
1
+ (function(l,d){typeof module<"u"&&module.exports?module.exports=d():l.FormLogicEngine=d()})(typeof globalThis<"u"?globalThis:this,function(){"use strict";function l(e){return e===""||e===null||e===void 0?"":String(e)}function d(e){const r=parseFloat(e);return isNaN(r)?null:r}function p(e){return e==null?!0:typeof e=="string"?e.trim()==="":Array.isArray(e)?e.length===0:!1}const P={not_equals:"equals",not_contains:"contains",not_in:"in"};function q(e,r){const t=l(r),n=l(e.value);switch(e.operator){case"equals":return t===n;case"not_equals":return t!==n;case"contains":return t.toLowerCase().includes(n.toLowerCase());case"not_contains":return!t.toLowerCase().includes(n.toLowerCase());case"starts_with":return t.toLowerCase().startsWith(n.toLowerCase());case"ends_with":return t.toLowerCase().endsWith(n.toLowerCase());case"greater_than":{const i=d(r),s=d(e.value);return i!==null&&s!==null&&i>s}case"less_than":{const i=d(r),s=d(e.value);return i!==null&&s!==null&&i<s}case"is_empty":return p(r);case"is_not_empty":return!p(r);case"in":return(Array.isArray(e.value)?e.value.map(l):String(e.value).split(",").map(s=>s.trim())).includes(t);case"not_in":return!(Array.isArray(e.value)?e.value.map(l):String(e.value).split(",").map(s=>s.trim())).includes(t);case"matches_regex":try{const i=e.flags||"";return new RegExp(e.value,i).test(t)}catch(i){return typeof console<"u"&&console.warn&&console.warn('[FormLogicEngine] Invalid regex in condition for field "'+e.field+'": '+e.value,i&&i.message),!1}default:return!0}}function _(e,r){if(!e||!e.field||!e.operator)return!0;const t=r[e.field];if(!Array.isArray(t))return q(e,t);if(e.operator==="is_empty")return p(t);if(e.operator==="is_not_empty")return!p(t);const n=P[e.operator];if(n){const i=Object.assign({},e,{operator:n});return!t.some(s=>q(i,s))}return t.some(i=>q(e,i))}function h(e,r){return e?Array.isArray(e.all)?e.all.every(t=>t.all||t.any?h(t,r):_(t,r)):Array.isArray(e.any)?e.any.some(t=>t.all||t.any?h(t,r):_(t,r)):e.field&&e.operator?_(e,r):!0:!0}function v(e,r){const t=e.logic;if(!t||!t.visibility)return"visible";const n=t.visibility,i=n.conditions||[];for(const s of i)if(s.when&&h(s.when,r))return s.then==="hidden"?"hidden":"visible";return n.default==="hidden"?"hidden":"visible"}function w(e,r){const t=e.logic;if(!t||!t.requirement)return e.required||!1;const n=t.requirement,i=n.conditions||[];for(const s of i)if(s.when&&h(s.when,r))return s.then===!0;return n.default===!0}function A(e){if(e==null||e==="")return null;const r=Number(e);return Number.isFinite(r)?r:null}function z(e,r){if(p(r)||Array.isArray(r)||typeof r=="object")return[];const t=e.label||e.name;if(e.type==="number"){const a=Number(r);if(!Number.isFinite(a)||String(r).trim()==="")return[{message:`${t} must be a number.`}];const o=A(e.min),u=A(e.max);return o!==null&&a<o?[{message:`${t} must be at least ${o}.`}]:u!==null&&a>u?[{message:`${t} must be no more than ${u}.`}]:[]}const n=String(r).length,i=A(e.minLength),s=A(e.maxLength);return i!==null&&n<i?[{message:`${t} must be at least ${i} characters.`}]:s!==null&&n>s?[{message:`${t} must be no more than ${s} characters.`}]:[]}function k(e,r,t){const n=z(e,r),i=e.logic;if(!i||!Array.isArray(i.validation))return n;for(const s of i.validation)switch(s.type){case"regex":{if(!p(r))try{new RegExp(s.pattern||"",s.flags||"").test(l(r))||n.push({message:s.message||"Invalid format."})}catch{}break}case"match":{const a=t[s.field];!p(r)&&l(r)!==l(a)&&n.push({message:s.message||"Fields do not match."});break}}return n}function T(e,r){const t=e.logic;if(!t||!t.cascade||!t.cascade.sourceField)return null;const n=t.cascade,i=n.mapping||{},s=r[n.sourceField];if(Array.isArray(s)){const o=new Set,u=[];return s.forEach(function(g){const f=l(g);Object.prototype.hasOwnProperty.call(i,f)&&(i[f]||[]).forEach(function(x){const H=l(x&&x.value);o.has(H)||(o.add(H),u.push(x))})}),u.length?u:n.defaultOptions||null}const a=l(s);return Object.prototype.hasOwnProperty.call(i,a)?i[a]||[]:n.defaultOptions||null}const j={fade:250,slide:300,scale:200};function N(e,r){const t=Number(r);return!isFinite(t)||t<0||t>5e3?j[e]||250:Math.round(t)}function G(e,r,t){const n=N(r,t);if(r==="fade")e.style.transition="opacity "+n+"ms ease",e.style.opacity="1",requestAnimationFrame(function(){e.style.opacity="0",setTimeout(function(){e.classList.add("fb-field-hidden"),e.style.transition="",e.style.opacity=""},n+10)});else if(r==="slide"){const i=Math.round(n*.67);e.style.overflow="hidden",e.style.maxHeight=e.scrollHeight+"px",e.style.transition="max-height "+n+"ms ease, opacity "+i+"ms ease",requestAnimationFrame(function(){e.style.maxHeight="0",e.style.opacity="0",setTimeout(function(){e.classList.add("fb-field-hidden"),e.style.transition="",e.style.maxHeight="",e.style.opacity="",e.style.overflow=""},n+20)})}else r==="scale"&&(e.style.transition="opacity "+n+"ms ease, transform "+n+"ms ease",e.style.transform="scale(1)",e.style.opacity="1",requestAnimationFrame(function(){e.style.transform="scale(0.95)",e.style.opacity="0",setTimeout(function(){e.classList.add("fb-field-hidden"),e.style.transition="",e.style.transform="",e.style.opacity=""},n+20)}))}function K(e,r,t){const n=N(r,t);if(e.classList.remove("fb-field-hidden"),r==="fade")e.style.opacity="0",e.style.transition="opacity "+n+"ms ease",requestAnimationFrame(function(){e.style.opacity="1",setTimeout(function(){e.style.transition="",e.style.opacity=""},n+10)});else if(r==="slide"){const i=Math.round(n*.67);e.style.overflow="hidden",e.style.maxHeight="0",e.style.opacity="0",e.style.transition="max-height "+n+"ms ease, opacity "+i+"ms ease",requestAnimationFrame(function(){e.style.maxHeight=e.scrollHeight+"px",e.style.opacity="1",setTimeout(function(){e.style.transition="",e.style.maxHeight="",e.style.opacity="",e.style.overflow=""},n+20)})}else r==="scale"&&(e.style.transform="scale(0.95)",e.style.opacity="0",e.style.transition="opacity "+n+"ms ease, transform "+n+"ms ease",requestAnimationFrame(function(){e.style.transform="scale(1)",e.style.opacity="1",setTimeout(function(){e.style.transition="",e.style.transform="",e.style.opacity=""},n+20)}))}const C={banner:"state","disable-fields":"state","hide-fields":"state","require-fields":"state","block-submit":"state","end-form":"state",toast:"event",celebrate:"event","goto-step":"event",redirect:"event","run-action":"event",notify:"event"},U=["block-submit","hide-fields","disable-fields","require-fields","end-form","redirect","run-action","notify"];function y(e){return!!e&&!!e.name&&e.type!=="page-break"&&e.type!=="spacer"}function b(e,r,t){const n=(t||[]).filter(y),i=e.target||(e.fields&&e.fields.length?"listed":"rest");if(i==="listed"){const a=new Set(e.fields||[]);return n.filter(o=>a.has(o.name)&&o.name!==r.name).map(o=>o.name)}if(i==="all")return n.filter(a=>a.name!==r.name).map(a=>a.name);const s=n.findIndex(a=>a.name===r.name);return s===-1?[]:n.slice(s+1).map(a=>a.name)}function S(e,r){for(const t of r)e.includes(t)||e.push(t)}function Y(e,r,t,n,i){if(!r||!r.type)return;const s=C[r.type];if(s){if(s==="event"){e.events.push({key:i,type:r.type,field:t.name,action:r});return}switch(r.type){case"banner":e.banners.push({key:i,field:t.name,message:r.message||"",level:r.level||"info",position:r.position||"field-below",dismissible:r.dismissible!==!1});break;case"hide-fields":S(e.hidden,b(r,t,n));break;case"disable-fields":S(e.disabled,b(r,t,n));break;case"require-fields":S(e.required,b(r,t,n));break;case"block-submit":e.blocks.push({key:i,message:r.message||"This form cannot be submitted yet."});break;case"end-form":S(e.masked,b({target:"rest"},t,n)),e.ends.push({key:i,field:t.name,message:r.message||"",finishLabel:r.finishLabel||"Finish",doneMessage:r.doneMessage||"",record:r.record!==!1,finish:r.finish!==!1}),r.finish===!1&&e.blocks.push({key:i,quiet:!0,message:r.message||"This form cannot be submitted."});break;default:break}}}const L=/\{\{\s*([A-Za-z_][A-Za-z0-9_-]*)\s*\}\}/g,X=["password","file","image","signature"],F=200,Z={amp:"&",lt:"<",gt:">",quot:'"',apos:"'",nbsp:" ",pound:"\xA3",euro:"\u20AC",copy:"\xA9",reg:"\xAE"};function I(e){return String(e).replace(/<[^>]*>/g,"").replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi,(r,t)=>{if(t[0]==="#"){const n=t[1]==="x"||t[1]==="X"?parseInt(t.slice(2),16):parseInt(t.slice(1),10);return Number.isFinite(n)&&n>31?String.fromCodePoint(n):" "}return Z[t.toLowerCase()]??r})}function M(e,r){const t=Array.isArray(e.options)?e.options:[];for(const n of t)if(n&&typeof n=="object"){if(String(n.value)===String(r))return I(n.label!=null?n.label:n.value)}else if(String(n)===String(r))return I(n);return String(r)}function J(e,r){if(!e||X.includes(e.type)||r==null||r===!1)return"";let t;return Array.isArray(r)?t=r.filter(n=>n!==""&&n!=null).map(n=>M(e,n)).join(", "):e.type==="checkbox"?t=r&&r!=="false"?"Yes":"":Array.isArray(e.options)&&e.options.length?t=r===""?"":M(e,r):t=String(r),t=t.replace(/[\u0000-\u001f\u007f]+/g," ").replace(/\s+/g," ").trim(),t.length>F?t.slice(0,F-1).trimEnd()+"\u2026":t}function O(e,r,t,n){if(!e||typeof e!="string"||e.indexOf("{{")===-1)return e||"";const i=n instanceof Set?n:new Set(n||[]),s=new Map((r||[]).filter(y).map(a=>[a.name,a]));return e.replace(L,(a,o)=>{const u=s.get(o);return!u||i.has(o)?"":J(u,(t||{})[o])})}function Q(e){const r=[];return String(e||"").replace(L,(t,n)=>(r.includes(n)||r.push(n),t)),r}function ee(e,r,t){const n=new Set([...e.hidden,...e.disabled,...e.masked]);(r||[]).forEach(s=>{y(s)&&v(s,t)==="hidden"&&n.add(s.name)});const i=s=>O(s,r,t,n);e.banners.forEach(s=>{s.message=i(s.message)}),e.blocks.forEach(s=>{s.message=i(s.message)}),e.ends.forEach(s=>{s.message=i(s.message),s.doneMessage=i(s.doneMessage)}),e.events.forEach(s=>{s.type==="toast"&&s.action&&s.action.message&&(s.action=Object.assign({},s.action,{message:i(s.action.message)}))})}function R(e,r){const t={active:[],banners:[],hidden:[],disabled:[],required:[],blocks:[],events:[],masked:[],ends:[]};return(e||[]).forEach(n=>{!y(n)||!Array.isArray(n.triggers)||n.triggers.forEach((i,s)=>{if(!i||i.enabled===!1)return;const a=i.id||`${n.name}::${s}`,o=h(i.when,r),u=`${a}:${o?"then":"else"}`,g=o?i.then:i.otherwise;!Array.isArray(g)||!g.length||(t.active.push(u),g.forEach(f=>Y(t,f,n,e,u)))})}),t.required=t.required.filter(n=>!t.hidden.includes(n)&&!t.masked.includes(n)),ee(t,e,r||{}),t}function B(e){return(e||[]).some(r=>Array.isArray(r&&r.triggers)&&r.triggers.length>0)}function c(e,r,t){this._form=e,this._wrapper=r,this._fields=(e.fields||[]).filter(n=>n.type!=="page-break"),this._listeners=[],this._depMap=new Map,this._initialEval=!1,this._baseValues=t&&t.baseValues||null,this._hasTriggers=B(this._fields),this._triggerState=null,this._triggerPrev=new Set,this._triggerFired=new Set,this._triggerApplied={hidden:[],disabled:[],required:[],masked:[]},this._banners=new Map,this._dismissed=new Set,this._end=null}function m(e,r,t){const n=t||"",i=r||"";return n+'[name="'+e+'"]'+i+", "+n+'[name="'+e+'[]"]'+i}function te(e){return String(e||"").replace(/\[\]$/,"")}c.prototype._fieldInputs=function(e){return this._wrapper.querySelectorAll(m(e))},c.prototype._fieldInput=function(e){return this._wrapper.querySelector(m(e))},c.prototype._buildDepMap=function(){this._depMap.clear();const e=this;e._fields.forEach(function(r){const t=r.logic;if(!t)return;function n(i){if(!i)return;(i.all||i.any||[]).forEach(function(a){a.field?(e._depMap.has(a.field)||e._depMap.set(a.field,new Set),e._depMap.get(a.field).add(r.name)):n(a)})}if(t.visibility&&(t.visibility.conditions||[]).forEach(function(i){n(i.when)}),t.requirement&&(t.requirement.conditions||[]).forEach(function(i){n(i.when)}),t.cascade&&t.cascade.sourceField){const i=t.cascade.sourceField;e._depMap.has(i)||e._depMap.set(i,new Set),e._depMap.get(i).add(r.name)}})};function $(e,r){const t={};return(r||[]).forEach(function(n){if(!n||!n.name)return;const i=e.querySelectorAll(m(n.name));if(!i.length)return;const s=i[0];if(s.type==="radio"){const a=e.querySelector(m(n.name,":checked"));t[n.name]=a?a.value:""}else if(s.type==="checkbox")if(i.length>1||/\[\]$/.test(s.name||"")){const a=[];i.forEach(function(o){o.checked&&a.push(o.value)}),t[n.name]=a}else t[n.name]=s.checked?s.value||"true":"";else t[n.name]=s.value}),t}function V(e,r){const t=$(e,r),n=[],i=[];return(r||[]).forEach(function(s){if(!s||!s.name||v(s,t)==="hidden")return;const a=t[s.name],o=Array.isArray(a)?a.length===0:!String(a??"").trim();if(w(s,t)&&o){n.push({name:s.name,label:s.label||s.name});return}if(o)return;const u=k(s,a,t);u.length&&i.push(u[0].message)}),{valid:n.length===0&&i.length===0,missing:n,errors:i,values:t}}c.prototype._getValues=function(){const e=this._baseValues?Object.assign({},this._baseValues):{};return this._fields.forEach(function(r){const t=this._fieldInputs(r.name);if(!t.length)return;const n=t[0];if(n.type==="radio"){const i=this._wrapper.querySelector(m(r.name,":checked"));e[r.name]=i?i.value:""}else if(n.type==="checkbox")if(t.length>1||/\[\]$/.test(n.name||"")){const i=[];t.forEach(function(s){s.checked&&i.push(s.value)}),e[r.name]=i}else e[r.name]=n.checked?n.value||"true":"";else e[r.name]=n.value},this),e},c.prototype._applyVisibility=function(e,r){const n=v(e,r)==="hidden",i=this._findFieldWrapper(e.name);if(!i)return;i.setAttribute("aria-hidden",String(n));const s=this._initialEval?"none":e.logic?.visibility?.transition||"none";if(s==="none"){i.classList.toggle("fb-field-hidden",n);return}const a=e.logic?.visibility?.duration;i.style.transition="",n?G(i,s,a):K(i,s,a)},c.prototype._applyRequirement=function(e,r){const t=w(e,r),n=this._fieldInput(e.name);if(!n)return;if(/\[\]$/.test(n.name||"")||n.closest&&n.closest(".domma-chooser")){const s=this._findFieldWrapper(e.name);n.removeAttribute("required"),s&&(s.setAttribute("aria-required",String(t)),s.classList.toggle("fb-field-required",t));return}t?n.setAttribute("required",""):n.removeAttribute("required");const i=this._findFieldWrapper(e.name);i&&(i.setAttribute("aria-required",String(t)),i.classList.toggle("fb-field-required",t))},c.prototype._applyCascade=function(e,r){const t=T(e,r);if(t===null)return;const n=this._wrapper.querySelector(m(e.name,"","select"));if(!n)return;const i=n.value;n.textContent="",t.forEach(function(s){const a=document.createElement("option");a.value=s.value,a.textContent=s.label||s.value,s.value===i&&(a.selected=!0),n.appendChild(a)})},c.prototype._applyValidation=function(e,r){const t=this._fieldInput(e.name);if(!t)return;const n=k(e,r[e.name],r);let i=t.parentNode.querySelector(".fb-validation-error");n.length?(i||(i=document.createElement("div"),i.className="fb-validation-error",t.parentNode.appendChild(i)),i.textContent=n[0].message):i&&i.remove()},c.prototype._findFieldWrapper=function(e){const r=this._fieldInput(e);if(!r)return null;const t=r.closest("form")||this._wrapper;let n=r;for(;n.parentNode&&n.parentNode!==t;)n=n.parentNode;return n},c.prototype.evaluate=function(e){const r=this._getValues(),t=this;let n;if(e&&this._depMap.has(e)){const i=this._depMap.get(e);n=this._fields.filter(function(s){return i.has(s.name)})}else n=this._fields;n.forEach(function(i){i.logic&&(t._applyVisibility(i,r),t._applyRequirement(i,r),t._applyCascade(i,r))}),this._applyTriggers(r)},c.prototype.init=function(){this._buildDepMap();const e=this;if(this._wrapper.querySelectorAll("input, select, textarea").forEach(function(n){const i=te(n.getAttribute("name"));if(!i)return;const s=function(){e.evaluate(i)};n.addEventListener("input",s),n.addEventListener("change",s),e._listeners.push({el:n,event:"input",fn:s}),e._listeners.push({el:n,event:"change",fn:s});const a=e._fields.find(function(o){return o.name===i});if(a&&a.logic&&Array.isArray(a.logic.validation)&&a.logic.validation.length){const o=function(){const u=e._getValues();e._applyValidation(a,u)};n.addEventListener("blur",o),e._listeners.push({el:n,event:"blur",fn:o})}}),this._wrapper.querySelectorAll("[data-chooser-field]").forEach(function(n){const i=n.getAttribute("data-chooser-field");if(!i)return;const s=function(){setTimeout(function(){e.evaluate(i)},0)};n.addEventListener("click",s),e._listeners.push({el:n,event:"click",fn:s})}),this._hasTriggers){const n=function(i){if(e._end&&!(i.type==="click"&&!(i.target&&i.target.closest&&i.target.closest(ne)))){if(!e._end.spec.finish){i.preventDefault(),i.stopImmediatePropagation();return}e._wrapper.dataset.triggerBlocked!=="true"&&(i.preventDefault(),i.stopImmediatePropagation(),e._finishEnded())}};this._wrapper.addEventListener("click",n,!0),this._wrapper.addEventListener("submit",n,!0),this._listeners.push({el:this._wrapper,event:"click",fn:n,capture:!0}),this._listeners.push({el:this._wrapper,event:"submit",fn:n,capture:!0})}this._initialEval=!0,this.evaluate(),this._initialEval=!1},c.prototype.destroy=function(){this._listeners.forEach(function(e){e.el.removeEventListener(e.event,e.fn,!!e.capture)}),this._listeners=[],this._banners.forEach(function(e){e&&e.parentNode&&e.parentNode.removeChild(e)}),this._banners.clear(),this._syncEnd([])},c.prototype._applyTriggers=function(e){if(!this._hasTriggers)return;const r=R(this._fields,e);this._triggerState=r;const t=this._triggerApplied||{hidden:[],disabled:[],required:[]};t.hidden.forEach(n=>{r.hidden.indexOf(n)===-1&&this._setTriggerHidden(n,!1)}),r.hidden.forEach(n=>this._setTriggerHidden(n,!0)),t.disabled.forEach(n=>{r.disabled.indexOf(n)===-1&&this._setTriggerDisabled(n,!1)}),r.disabled.forEach(n=>this._setTriggerDisabled(n,!0)),t.required.forEach(n=>{r.required.indexOf(n)===-1&&this._restoreRequired(n,e)}),r.required.forEach(n=>this._setTriggerRequired(n,!0)),(t.masked||[]).forEach(n=>{r.masked.indexOf(n)===-1&&this._setTriggerMasked(n,!1)}),r.masked.forEach(n=>this._setTriggerMasked(n,!0)),this._triggerApplied={hidden:r.hidden.slice(),disabled:r.disabled.slice(),required:r.required.slice(),masked:r.masked.slice()},this._syncBanners(r.banners),this._syncSubmitBlock(r.blocks),this._syncEnd(r.ends),this._fireTriggerEvents(r)},c.prototype._setTriggerHidden=function(e,r){const t=this._findFieldWrapper(e);t&&(t.classList.toggle("fb-field-trigger-hidden",r),t.setAttribute("aria-hidden",String(r||t.classList.contains("fb-field-hidden"))))},c.prototype._setTriggerMasked=function(e,r){const t=this._findFieldWrapper(e);t&&(t.classList.toggle("fb-field-trigger-masked",r),t.setAttribute("aria-hidden",String(r||t.classList.contains("fb-field-hidden")||t.classList.contains("fb-field-trigger-hidden"))))},c.prototype._setTriggerDisabled=function(e,r){const t=this._findFieldWrapper(e);t&&t.classList.toggle("fb-field-disabled",r),this._fieldInputs(e).forEach(function(i){i.disabled=r});const n=this._wrapper.querySelector('[data-chooser-field="'+e+'"]');n&&(n.classList.toggle("fb-field-disabled",r),n.querySelectorAll('button, [role="radio"], [role="checkbox"]').forEach(function(i){r?i.setAttribute("aria-disabled","true"):i.removeAttribute("aria-disabled")}))},c.prototype._setTriggerRequired=function(e,r){const t=this._fieldInput(e),n=this._findFieldWrapper(e);if(n&&(n.setAttribute("aria-required",String(r)),n.classList.toggle("fb-field-required",r)),!!t){if(/\[\]$/.test(t.name||"")||t.closest&&t.closest(".domma-chooser")){t.removeAttribute("required");return}r?t.setAttribute("required",""):t.removeAttribute("required")}},c.prototype._restoreRequired=function(e,r){const t=this._fields.find(function(n){return n.name===e});t&&this._setTriggerRequired(e,w(t,r))},c.prototype._syncBanners=function(e){const r=new Map;e.forEach(function(t){r.set(t.key,t)}),this._banners.forEach(function(t,n){r.has(n)||(t.parentNode&&t.parentNode.removeChild(t),this._banners.delete(n))},this),this._dismissed.forEach(function(t){r.has(t)||this._dismissed.delete(t)},this),r.forEach(function(t,n){if(this._banners.has(n)){const a=this._banners.get(n).querySelector(".fb-trigger-banner-text");a&&a.textContent!==t.message&&(a.textContent=t.message);return}if(this._dismissed.has(n))return;const i=this._buildBanner(t,n);i&&this._banners.set(n,i)},this)},c.prototype._buildBanner=function(e,r){if(typeof document>"u"||!e.message)return null;const t=document.createElement("div");t.className="fb-trigger-banner fb-trigger-banner--"+(e.level||"info"),t.setAttribute("role",e.level==="error"?"alert":"status"),t.dataset.triggerKey=r;const n=document.createElement("span");if(n.className="fb-trigger-banner-text",n.textContent=e.message,t.appendChild(n),e.dismissible){const a=document.createElement("button");a.type="button",a.className="fb-trigger-banner-close",a.setAttribute("aria-label","Dismiss"),a.textContent="\xD7";const o=this;a.addEventListener("click",function(){o._dismissed.add(r),t.parentNode&&t.parentNode.removeChild(t),o._banners.delete(r)}),t.appendChild(a)}const i=this._findFieldWrapper(e.field),s=this._wrapper.querySelector("form")||this._wrapper;return e.position==="form-top"?s.insertBefore(t,s.firstChild):i&&i.parentNode?e.position==="field-above"?i.parentNode.insertBefore(t,i):i.parentNode.insertBefore(t,i.nextSibling):s.appendChild(t),t},c.prototype._syncSubmitBlock=function(e){const r=this._wrapper.querySelector("form")||this._wrapper,t=r.querySelector('button[type="submit"], input[type="submit"], .fb-form-submit'),n=e.length>0;this._wrapper.dataset.triggerBlocked=n?"true":"false",t&&(t.disabled=n,t.setAttribute("aria-disabled",String(n)));let i=r.querySelector(".fb-trigger-block-note");const s=e.filter(function(a){return!a.quiet}).map(function(a){return a.message});if(!s.length){i&&i.parentNode&&i.parentNode.removeChild(i);return}i||(i=document.createElement("p"),i.className="fb-trigger-block-note",i.setAttribute("role","status"),(t&&t.parentNode?t.parentNode:r).appendChild(i)),i.textContent=s.join(" ")};const ne='button[type="submit"], input[type="submit"], .fb-form-submit, .wizard-next, .wizard-submit';c.prototype._primaryButton=function(){return this._wrapper.querySelector(".wizard-next, .wizard-submit")||this._wrapper.querySelector('button[type="submit"], input[type="submit"], .fb-form-submit')};function W(e){return!!(e.matches&&e.matches(".wizard-next, .wizard-submit"))}function D(e,r){e.tagName==="INPUT"?e.value=r:e.textContent=r}return c.prototype._syncEnd=function(e){const r=e&&e.length?e[0]:null,t=this._end;if(t&&(!r||t.spec.key!==r.key)&&(t.panel&&t.panel.parentNode&&t.panel.parentNode.removeChild(t.panel),t.button&&t.button.isConnected&&(t.spec.finish?D(t.button,t.label):W(t.button)&&(t.button.disabled=!1)),this._end=null),this._wrapper.dataset.triggerEnded=r?"true":"false",!r||typeof document>"u")return;if(this._end){const a=this._end.panel&&this._end.panel.querySelector(".fb-trigger-end-text");a&&a.textContent!==r.message&&(a.textContent=r.message),this._end.spec=r;return}let n=null;if(r.message){n=document.createElement("div"),n.className="fb-trigger-end",n.setAttribute("role","status"),n.dataset.triggerKey=r.key;const a=document.createElement("div");a.className="fb-trigger-end-text",a.textContent=r.message,n.appendChild(a);const o=this._findFieldWrapper(r.field);o&&o.parentNode?o.parentNode.insertBefore(n,o.nextSibling):(this._wrapper.querySelector("form")||this._wrapper).appendChild(n)}const i=this._primaryButton();let s="";i&&r.finish?(s=i.tagName==="INPUT"?i.value:i.textContent,D(i,r.finishLabel)):i&&W(i)&&(i.disabled=!0),this._end={spec:r,panel:n,button:i,label:s}},c.prototype._finishEnded=function(){const e=this._triggerState||{},r=new Set([].concat(e.masked||[],e.hidden||[],e.disabled||[])),t=this,n=this._fields.filter(function(a){return y(a)&&!r.has(a.name)&&t._fieldInput(a.name)}),i=this._getValues();(e.masked||[]).forEach(function(a){delete i[a]});const s=V(this._wrapper,n);(e.required||[]).forEach(function(a){const o=n.find(function(f){return f.name===a});if(!o)return;const u=i[a];(Array.isArray(u)?u.length===0:!String(u??"").trim())&&!s.missing.some(function(f){return f.name===a})&&(s.missing.push({name:a,label:o.label||a}),s.valid=!1)}),this._wrapper.dispatchEvent(new CustomEvent("dm-form-end",{bubbles:!0,detail:{values:i,validation:s,end:this._end?this._end.spec:null}}))},c.prototype._fireTriggerEvents=function(e){const r=new Set(e.active),t=this;e.events.forEach(function(n){t._triggerPrev.has(n.key)||n.action.once&&t._triggerFired.has(n.key)||t._initialEval&&n.action.onLoad!==!0||(t._triggerFired.add(n.key),t._runTriggerEvent(n))}),this._triggerPrev=r},c.prototype._runTriggerEvent=function(e){const r=e.action;switch(r.type){case"toast":typeof E<"u"&&E&&typeof E.toast=="function"&&E.toast(r.message||"",{type:r.level||"info",duration:Number(r.duration)||4e3});break;case"celebrate":this._celebrate(r);break;default:break}typeof CustomEvent<"u"&&this._wrapper.dispatchEvent&&this._wrapper.dispatchEvent(new CustomEvent("dm-form-trigger",{bubbles:!0,detail:{type:r.type,field:e.field,action:r}}))},c.prototype._celebrate=function(e){typeof window>"u"||typeof document>"u"||window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches||import("/public/js/celebrations/index.js").then(function(r){const t=r.CelebrationsEffect;let n=e.theme||"auto";if(n==="auto"&&(n=t.getCurrentTheme(),!n))return;const i=new t({theme:n,intensity:e.intensity||"medium",enabled:!0,zIndex:999});i.init().then(function(){i.start()})}).catch(function(){})},c.prototype.submitBlock=function(){const e=this._triggerState&&this._triggerState.blocks||[];return{blocked:e.length>0,reasons:e.map(function(r){return r.message})}},{evaluateCondition:_,evaluateConditionGroup:h,evaluateFieldVisibility:v,evaluateFieldRequirement:w,validateField:k,collectValues:$,validateFields:V,getCascadeOptions:T,resolveTriggerState:R,resolveTriggerTargets:b,fillTriggerText:O,triggerTokens:Q,hasTriggers:B,TRIGGER_ACTION_KINDS:C,SERVER_TRIGGER_ACTIONS:U,FormLogicRuntime:c}});
@@ -150,7 +150,7 @@
150
150
  <script src="/public/js/collection-browser.js?v=20260927-scope-mine"></script>
151
151
 
152
152
  <!-- Core Forms (logic engine must load before renderer) -->
153
- <script src="/public/js/form-logic-engine.js?v=20260926-limits"></script>
153
+ <script src="/public/js/form-logic-engine.js?v=20260927-field-tokens"></script>
154
154
  <script src="/public/js/forms.js?v=20260926-form-end" type="module"></script>
155
155
 
156
156
  <!-- Core effects runtime -->