PageSourceSearch

https://ewafrica.com/wp-content/themes/wp-theme-ewa-2026/app/forms.js?v=1789609978

js ewafrica.com collected 2026-09-30 21:22:14 UTC 26,740 bytes, 679 lines download raw bytes

1/* Compiled from forms.jsx — DO NOT EDIT BY HAND. Edit the .jsx and rebuild (see BUILD.md). */
2(function () {
3/* global React */
4/* ============================================================
5   EWA 2026, Dynamic form engine
6   Renders a form from a field schema (editable in WordPress under
7   EWA Content), so fields and field types can be changed without code.
8
9   Field schema (array of objects):
10     { name, label, type, required, placeholder, options, width, help,
11       condField, condValues }
12   type: text | email | tel | number | url | date | textarea | select
13         | radio | checkboxes (pick many) | checkbox (single opt-in)
14         | consent (alias of checkbox) | file (stores "name (size KB)")
15   options: array of strings (select / radio / checkboxes) OR newline-separated
16   width: 'full'/'100' | 'half'/'50'/'33'  (textarea/select default to full)
17
18   PROPS (beyond fields/formId): heading, intro, submitLabel, successTitleHtml,
19   successBody, backHref/backLabel, onDone(payload), noSuccess (parent renders
20   its own success screen), persistKey (draft auto-saved to localStorage and
21   cleared on submit), reviewStep (adds a check-your-answers screen before
22   the real submit; reviewLabel overrides “Continue to Review”), plus
23   onReviewingChange(bool) + editSignal (bump the number to force back to
24   edit mode — lets a parent stepper control the review stage).
25
26   CONDITIONAL FIELDS
27   A field with condField only renders when the field with that id/name
28   has an answer. condValues (comma-separated, case-insensitive) narrows
29   it to specific answers — e.g. condField "role", condValues "Speaker,
30   Performer". Hidden fields are skipped by validation and excluded from
31   the submitted payload.
32   ============================================================ */
33(function () {
34  const {
35    useState,
36    useEffect
37  } = React;
38  const DFArr = () => /*#__PURE__*/React.createElement("span", {
39    className: "btn-arrow"
40  }, /*#__PURE__*/React.createElement("svg", {
41    width: "10",
42    height: "10",
43    viewBox: "0 0 10 10",
44    fill: "none"
45  }, /*#__PURE__*/React.createElement("path", {
46    d: "M2 8L8 2M8 2H3M8 2V7",
47    stroke: "currentColor",
48    strokeWidth: "1.6",
49    strokeLinecap: "round"
50  })));
51  const EMAIL_RE = /.+@.+\..+/;
52  const labelSlug = s => String(s || '').toLowerCase().replace(/<[^>]+>/g, ' ').replace(/[^a-z0-9]+/g, '_').replace(/(^_+|_+$)/g, '').slice(0, 40);
53  const keyOf = (f, i) => f && (f.name || f.id) ? f.name || f.id : labelSlug(f && f.label) || 'field_' + i;
54  const optVal = o => {
55    if (o == null) return '';
56    if (typeof o === 'object') {
57      const v = o.opt != null ? o.opt : o.option != null ? o.option : o.label != null ? o.label : o.value != null ? o.value : '';
58      return String(v == null ? '' : v);
59    }
60    return String(o);
61  };
62  const optsOf = f => (Array.isArray(f.options) ? f.options : typeof f.options === 'string' ? f.options.split(/\n|,/) : []).map(optVal).map(s => s.trim()).filter(Boolean);
63  const isCheck = t => t === 'checkbox' || t === 'consent';
64  const isMulti = t => t === 'checkboxes';
65
66  /* ---- conditional visibility ---- */
67  const condValsOf = f => String(f.condValues || '').split(/[,|]/).map(s => s.trim().toLowerCase()).filter(Boolean);
68  function fieldVisible(f, form) {
69    if (!f || !f.condField) return true;
70    const v = form[f.condField];
71    const vals = condValsOf(f);
72    const answered = Array.isArray(v) ? v.length > 0 : typeof v === 'boolean' ? v : !!(v && String(v).trim());
73    if (!vals.length) return answered; // blank = "has any answer"
74    if (!answered) return false;
75    const match = x => vals.indexOf(String(x).trim().toLowerCase()) >= 0;
76    if (Array.isArray(v)) return v.some(match);
77    if (typeof v === 'boolean') return v && (vals.indexOf('yes') >= 0 || vals.indexOf('true') >= 0 || vals.indexOf('1') >= 0 || vals.indexOf('checked') >= 0);
78    return match(v);
79  }
80  function Field({
81    f,
82    id,
83    wrapId,
84    value,
85    onChange,
86    err,
87    forceFull
88  }) {
89    const req = f.required ? /*#__PURE__*/React.createElement("span", {
90      className: "req"
91    }, "*") : null;
92    const ph = f.placeholder || '';
93    const errEl = err ? /*#__PURE__*/React.createElement("span", {
94      className: "ap-err",
95      role: "alert"
96    }, err) : null;
97    const errCls = err ? ' is-error' : '';
98    if (isCheck(f.type)) {
99      return /*#__PURE__*/React.createElement("div", {
100        className: 'ap-consent ap-field full' + errCls,
101        id: wrapId
102      }, /*#__PURE__*/React.createElement("input", {
103        id: id,
104        type: "checkbox",
105        checked: !!value,
106        onChange: e => onChange(e.target.checked)
107      }), /*#__PURE__*/React.createElement("label", {
108        htmlFor: id
109      }, /*#__PURE__*/React.createElement("span", {
110        dangerouslySetInnerHTML: {
111          __html: f.label
112        }
113      }), req), errEl);
114    }
115    if (f.type === 'radio') {
116      return /*#__PURE__*/React.createElement("div", {
117        className: 'ap-field full' + errCls,
118        id: wrapId
119      }, /*#__PURE__*/React.createElement("label", null, f.label, req), /*#__PURE__*/React.createElement("div", {
120        className: "df-radio-row"
121      }, optsOf(f).map(o => /*#__PURE__*/React.createElement("label", {
122        className: 'df-radio' + (value === o ? ' is-on' : ''),
123        key: o
124      }, /*#__PURE__*/React.createElement("input", {
125        type: "radio",
126        name: id,
127        value: o,
128        checked: value === o,
129        onChange: () =>
129 onChange(o)
130      }), /*#__PURE__*/React.createElement("span", null, o)))), f.help ? /*#__PURE__*/React.createElement("span", {
131        className: "df-help"
132      }, f.help) : null, errEl);
133    }
134    if (isMulti(f.type)) {
135      const cur = Array.isArray(value) ? value : [];
136      const toggle = o => onChange(cur.indexOf(o) >= 0 ? cur.filter(x => x !== o) : cur.concat([o]));
137      return /*#__PURE__*/React.createElement("div", {
138        className: 'ap-field full' + errCls,
139        id: wrapId
140      }, /*#__PURE__*/React.createElement("label", null, f.label, req), /*#__PURE__*/React.createElement("div", {
141        className: "df-radio-row"
142      }, optsOf(f).map(o => /*#__PURE__*/React.createElement("label", {
143        className: 'df-radio' + (cur.indexOf(o) >= 0 ? ' is-on' : ''),
144        key: o
145      }, /*#__PURE__*/React.createElement("input", {
146        type: "checkbox",
147        value: o,
148        checked: cur.indexOf(o) >= 0,
149        onChange: () => toggle(o)
150      }), /*#__PURE__*/React.createElement("span", null, o)))), f.help ? /*#__PURE__*/React.createElement("span", {
151        className: "df-help"
152      }, f.help) : null, errEl);
153    }
154    if (f.type === 'file') {
155      const pick = e => {
156        const file = e.target.files && e.target.files[0];
157        onChange(file ? file.name + ' (' + Math.max(1, Math.round(file.size / 1024)) + ' KB)' : '');
158      };
159      return /*#__PURE__*/React.createElement("div", {
160        className: 'ap-field full' + errCls,
161        id: wrapId
162      }, /*#__PURE__*/React.createElement("label", null, f.label, req), /*#__PURE__*/React.createElement("label", {
163        className: 'df-file' + (value ? ' has-file' : '') + errCls
164      }, /*#__PURE__*/React.createElement("input", {
165        id: id,
166        type: "file",
167        onChange: pick
168      }), /*#__PURE__*/React.createElement("span", {
169        className: "df-file-ic",
170        "aria-hidden": "true"
171      }, /*#__PURE__*/React.createElement("svg", {
172        width: "15",
173        height: "15",
174        viewBox: "0 0 16 16",
175        fill: "none"
176      }, /*#__PURE__*/React.createElement("path", {
177        d: "M8 11V3m0 0L5 6m3-3l3 3M3 13h10",
178        stroke: "currentColor",
179        strokeWidth: "1.6",
180        strokeLinecap: "round",
181        strokeLinejoin: "round"
182      }))), /*#__PURE__*/React.createElement("span", {
183        className: "df-file-txt"
184      }, value || f.placeholder || 'Choose a file\u2026'), /*#__PURE__*/React.createElement("span", {
185        className: "df-file-btn"
186      }, value ? 'Replace' : 'Browse')), f.help ? /*#__PURE__*/React.createElement("span", {
187        className: "df-help"
188      }, f.help) : null, errEl);
189    }
190    const w = String(f.width == null ? '' : f.width);
191    const wide = forceFull || w === 'full' || w === '100' || w !== '50' && w !== '33' && w !== 'half' && (f.type === 'textarea' || f.type === 'select');
192    let input;
193    if (f.type === 'textarea') {
194      input = /*#__PURE__*/React.createElement("textarea", {
195        id: id,
196        className: "ap-textarea",
197        value: value || '',
198        onChange: e => onChange(e.target.value),
199        placeholder: ph
200      });
201    } else if (f.type === 'select') {
202      input = /*#__PURE__*/React.createElement("select", {
203        id: id,
204        className: "ap-select",
205        value: value || '',
206        onChange: e => onChange(e.target.value)
207      }, /*#__PURE__*/React.createElement("option", {
208        value: ""
209      }, ph || 'Select…'), optsOf(f).map(o => /*#__PURE__*/React.createElement("option", {
210        key: o,
211        value: o
212      }, o)));
213    } else {
214      input = /*#__PURE__*/React.createElement("input", {
215        id: id,
216        className: "ap-input",
217        type: f.type || 'text',
218        value: value || '',
219        onChange: e => onChange(e.target.value),
220        placeholder: ph
221      });
222    }
223    return /*#__PURE__*/React.createElement("div", {
224      className: 'ap-field' + (wide ? ' full' : '') + errCls,
225      id: wrapId
226    }, /*#__PURE__*/React.createElement("label", null, f.label, req), input, f.help ? /*#__PURE__*/React.createElement("span", {
227      className: "df-help"
228    }, f.help) : null, errEl);
229  }
230  /* Stable per-draft id (survives reloads) for abandoned-application capture. */
231  function draftKeyFor(persistKey) {
232    let k = '';
233    try {
234      k = localStorage.getItem(persistKey + ':k') || '';
235    } catch (e) {}
236    if (!k) {
237      k = (Math.random().toString(36).slice(2, 10) + Date.now().toString(36)).slice(0, 20);
238      try {
239        localStorage.setItem(persistKey + ':k', k);
240      } catch (e) {}
241    }
242    return k;
243  }
244
245  function DynamicForm(props) {
246    const fields = Array.isArray(props.fields) ? props.fields : [];
247    const formId = props.formId || 'ewa_form';
248    const [form, setForm] = useState(() => {
249      const o = {};
250      fields.forEach((f, i) => {
251        o[keyOf(f, i)] = isCheck(f.type) ? false : isMulti(f.type) ? [] : '';
252      });
253      // Draft restore — so a refresh mid-application never loses work.
254      if (props.persistKey) {
255        try {
256          const raw = localStorage.getItem(props.persistKey);
257          if (raw) {
258            const d = JSON.parse(raw);
259            Object.keys(o).forEach(k => {
260              if (d && d[k] !== undefined) o[k] = d[k];
261            });
262          }
263        } catch (e) {}
264      }
265      return o;
266    });
267    const [done, setDone] = useState(false);
268    const [reviewing, setReviewing] = useState(false);
269    const [errors, setErrors] = useState({});
270    const [hp, setHp] = useState(''); // honeypot — humans never see or fill it
271    const set = (k, v) => {
272      setForm(s => ({
273        ...s,
274        [k]: v
275      }));
276      setErrors(er => er[k] ? Object.assign({}, er, {
277        [k]: ''
278      }) : er);
279    };
280    useEffect(() => {
281      if (!props.persistKey || done) return;
282      try {
283        localStorage.setItem(props.persistKey, JSON.stringify(form));
284      } catch (e) {}
285    }, [form, done]);
286    /* External prefill (contact route cards pre-select the enquiry type). */
287    useEffect(() => {
288      const h = e => {
289        const d = e && e.detail || {};
290        setForm(s => {
291          const n = Object.assign({}, s);
292          fields.forEach((f, i) => { if (f && f.name && d[f.name] !== undefined) n[keyOf(f, i)] = d[f.name]; });
293          return n;
294        });
295      };
296      window.addEventListener('ewa:prefill', h);
297      return () => window.removeEventListener('ewa:prefill', h);
298    }, []);
299    /* Abandoned-application capture: once a valid email is typed on a
300       persistKey form, snapshot progress to the server (debounced; final
301       flush on tab-hide) so the team sees drop-offs and the 30-minute
302       resume reminder can fire. No-op without CMS.saveDraft. */
303    useEffect(() => {
304      if (!props.persistKey || done || !(window.CMS && window.CMS.saveDraft)) return;
305      const snap = () => {
306        const ei = fields.findIndex(f => f && f.type === 'email');
307        const email = ei >= 0 ? String(form[keyOf(fields[ei], ei)] || '').trim() : '';
308        if (!/^\S+@\S+\.\S+$/.test(email)) return null;
309        const vis = fields.filter(f => fieldVisible(f, form));
310        const data = {};
311        let nf = 0;
312        vis.forEach(f => {
313          const k = keyOf(f, fields.indexOf(f));
314          const v = form[k];
315          const has = isCheck(f.type) ? !!v : isMulti(f.type) ? Array.isArray(v) && v.length > 0 : !!(v && String(v).trim());
316          if (has) {
317            nf++;
318            data[k] = Array.isArray(v) ? v.slice(0, 12) : typeof v === 'boolean' ? v : String(v).slice(0, 300);
319          }
320        });
321        const nm = ['firstName', 'fullName', 'name', 'contactName'].map(k => form[k]).filter(v => v && String(v).trim())[0] || '';
322        return {
323          key: draftKeyFor(props.persistKey),
324          email: email,
325          name: String(nm).slice(0, 80),
326          step: reviewing ? 'review' : 'form',
327          filled: nf,
328          total: vis.length,
329          pg: location.pathname,
330          data: data
331        };
332      };
333      const send = beacon => {
334        const d = snap();
335        if (d) window.CMS.saveDraft(formId, d, beacon);
336      };
337      const t = setTimeout(() => send(false), 5000);
338      const onHide = () => {
339        if (document.visibilityState === 'hidden') send(true);
340      };
341      document.addEventListener('visibilitychange', onHide);
342      return () => {
343        clearTimeout(t);
344        document.removeEventListener('visibilitychange', onHide);
345      };
346    }, [form, reviewing, done]);
347    useEffect(() => {
348      if (props.onReviewingChange) props.onReviewingChange(reviewing);
349    }, [reviewing]);
350    useEffect(() => {
351      if (props.editSignal) setReviewing(false);
352    }, [props.editSignal]);
353    useEffect(() => {
354      if (!props.reviewStep && reviewing) setReviewing(false);
355    }, [props.reviewStep]);
356    const shown = fields.filter(f => fieldVisible(f, form));
357    const filled = (f, i) => {
358      const v = form[keyOf(f, i)];
359      if (isCheck(f.type)) return !!v;
360      if (isMulti(f.type)) return Array.isArray(v) && v.length > 0;
361      if (f.type === 'email') return EMAIL_RE.test(v || '');
362      return !!(v && String(v).trim());
363    };
364    /* Human error message for one field ('' = fine). Required first, then
365       format checks (an optional-but-filled email/link must still be valid). */
366    const coerceUrl = s => {
367      let t = String(s == null ? '' : s).trim();
368      if (!t) return t;
369      if (!/^https?:\/\//i.test(t)) t = 'https://' + t;
370      try {
371        new URL(t);
372        return /\./.test(t.replace(/^https?:\/\//i, '')) ? t : null;
373      } catch (e) {
374        return null;
375      }
376    };
377    const errOf = (f, i) => {
378      const v = form[keyOf(f, i)];
379      if (f.required && !filled(f, i)) {
380        if (isCheck(f.type)) return 'Please tick this box to continue.';
381        if (isMulti(f.type)) return 'Please pick at least one option.';
382        if (f.type === 'file') return 'Please attach a file.';
383        if (f.type === 'email') return v && String(v).trim() ? 'Please enter a valid email address.' : 'Please enter your email address.';
384        if (f.type === 'select' || f.type === 'radio') return 'Please choose an option.';
385        return 'This field is required — please fill it in.';
386      }
387      if (f.type === 'email' && v && String(v).trim() && !EMAIL_RE.test(v)) return 'Please enter a valid email address.';
388      if (f.type === 'url' && v && String(v).trim() && coerceUrl(v) === null) return 'Please enter a valid link (e.g. https://…).';
389      return '';
390    };
391    const validateAll = () => {
392      const e = {};
393      shown.forEach(f => {
394        const i = fields.indexOf(f);
395        const m = errOf(f, i);
396        if (m) e[keyOf(f, i)] = m;
397      });
398      return e;
399    };
400    const valid = Object.keys(validateAll()).length === 0;
401    const errCount = shown.reduce((n, f) => n + (errors[keyOf(f, fields.indexOf(f))] ? 1 : 0), 0);
402    const btnClass = 'btn ' + (props.submitClass || 'btn-lime');
403
404    /* scroll the first broken field into view and focus it */
405    const focusField = k => {
406      setTimeout(() => {
407        const wrap = document.getElementById('dfw-' + formId + '-' + k);
408        const inp = document.getElementById('df-' + formId + '-' + k);
409        const el = wrap || inp;
410        if (el) {
411          try {
412            window.scrollTo({
413              top: el.getBoundingClientRect().top + window.scrollY - 150,
414              behavior: 'smooth'
415            });
416          } catch (e) {}
417        }
418        if (inp && inp.focus) {
419          try {
420            inp.focus({
421              preventScroll: true
422            });
423          } catch (e) {
424            try {
425              inp.focus();
426            } catch (e2) {}
427          }
428        }
429      }, 40);
430    };
431    const finalSubmit = () => {
432      /* re-validate at the last gate: a silent return here looked like a dead
433         Submit button (rage clicks). Broken answers bounce back to edit mode
434         with the first bad field focused + error text visible. */
435      const lastErrs = validateAll();
436      if (Object.keys(lastErrs).length) {
437        setErrors(lastErrs);
438        setReviewing(false);
439        const firstBad = shown.map(f =>
439 keyOf(f, fields.indexOf(f))).filter(k => lastErrs[k]);
440        if (firstBad.length) focusField(firstBad[0]);
441        return;
442      }
443      /* payload hygiene: trim strings, cap runaway lengths, normalise links,
444         and carry the empty honeypot so the server-side bot trap is live */
445      const cleanVal = (f, v) => {
446        if (typeof v === 'string') {
447          let s = v.trim().slice(0, 4000);
448          if (f.type === 'url' && s) {
449            const c = coerceUrl(s);
450            if (c) s = c;
451          }
452          return s;
453        }
454        if (Array.isArray(v)) return v.map(x => String(x).slice(0, 400));
455        return v;
456      };
457      const payload = {
458        _hp: hp
459      };
460      shown.forEach(f => {
461        const i = fields.indexOf(f);
462        payload[keyOf(f, i)] = cleanVal(f, form[keyOf(f, i)]);
463      });
464      const summary = shown.filter(f => f.type === 'text' || f.type === 'email').slice(0, 2).map(f => form[keyOf(f, fields.indexOf(f))]).filter(Boolean).join(' · ');
465      if (window.CMS && window.CMS.submit) window.CMS.submit(formId, payload, summary);
466      if (props.persistKey && window.CMS && window.CMS.completeDraft) window.CMS.completeDraft(formId, draftKeyFor(props.persistKey));
467      if (props.persistKey) {
468        try {
469          localStorage.removeItem(props.persistKey);
470          localStorage.removeItem(props.persistKey + ':k');
471        } catch (e3) {}
472      }
473      setDone(true);
474      if (props.onDone) props.onDone(payload);
475      if (!props.noSuccess) {
476        try {
477          window.scrollTo({
478            top: 0,
479            behavior: 'smooth'
480          });
481        } catch (e2) {}
482      }
483    };
484    const submit = e => {
485      if (e) e.preventDefault();
486      const errs = validateAll();
487      setErrors(errs);
488      const bad = shown.map(f => keyOf(f, fields.indexOf(f))).filter(k => errs[k]);
489      if (bad.length) {
490        focusField(bad[0]);
491        return;
492      }
493      if (props.reviewStep && !reviewing) {
494        setReviewing(true);
495        return;
496      }
497      finalSubmit();
498    };
499    const backToEdit = focusKey => {
500      setReviewing(false);
501      if (focusKey) {
502        setTimeout(() => {
503          const el = document.getElementById('df-' + formId + '-' + focusKey);
504          if (!el) return;
505          try {
506            window.scrollTo({
507              top: el.getBoundingClientRect().top + window.scrollY - 140,
508              behavior: 'smooth'
509            });
510          } catch (e) {}
511          if (el.focus) el.focus();
512        }, 60);
513      }
514    };
515    if (done && props.noSuccess) return null;
516    if (done) {
517      return /*#__PURE__*/React.createElement("div", {
518        className: "ct-success"
519      }, /*#__PURE__*/React.createElement("div", {
520        className: "ct-success-mark"
521      }, /*#__PURE__*/React.createElement("svg", {
522        width: "38",
523        height: "38",
524        viewBox: "0 0 24 24",
525        fill: "none"
526      }, /*#__PURE__*/React.createElement("path", {
527        d: "M4 12.5L9.5 18L20 6",
528        stroke: "currentColor",
529        strokeWidth: "2.4",
530        strokeLinecap: "round",
531        strokeLinejoin: "round"
532      }))), /*#__PURE__*/React.createElement("h2", {
533        dangerouslySetInnerHTML: {
534          __html: props.successTitleHtml || 'Message <em>sent</em>'
535        }
536      }), /*#__PURE__*/React.createElement("p", null, props.successBody || "Thanks, your enquiry is in and the right team will get back to you shortly."), props.backHref ? /*#__PURE__*/React.createElement("a", {
537        className: btnClass,
538        href: props.backHref
539      }, props.backLabel || 'Back to Home') : null);
540    }
541
542    /* —— REVIEW: check-your-answers screen (reviewStep) —— */
543    if (reviewing && !done) {
544      const stripTags = s => String(s == null ? '' : s).replace(/<[^>]+>/g, '');
545      const fmtVal = (f, v) => {
546        if (isCheck(f.type)) return v ? 'Yes, agreed' : '—';
547        if (Array.isArray(v)) return v.length ? v.join(', ') : '—';
548        const s = (v == null ? '' : String(v)).trim();
549        return s || '—';
550      };
551      return /*#__PURE__*/React.createElement("div", {
552        className: "df-review"
553      }, /*#__PURE__*/React.createElement("div", {
554        className: "ap-review"
555      }, shown.map(f => {
556        const i = fields.indexOf(f);
557        const k = keyOf(f, i);
558        return /*#__PURE__*/React.createElement("div", {
559          className: "ap-review-row",
560          key: k
561        }, /*#__PURE__*/React.createElement("span", {
562          className: "ap-review-k"
563        }, stripTags(f.label || k)), /*#__PURE__*/React.createElement("span", {
564          className: "ap-review-v"
565        }, fmtVal(f, form[k])), /*#__PURE__*/React.createElement("button", {
566          type: "button",
567          className: "ap-review-edit",
568          onClick: () => backToEdit(k)
569        }, "Edit"));
570      })), /*#__PURE__*/React.createElement("div", {
571        className: "ap-actions",
572        style: {
573          marginTop: 26
574        }
575      }, /*#__PURE__*/React.createElement("button", {
576        type: "button",
577        className: "btn btn-outline",
578        onClick: () => backToEdit('')
579      }, /*#__PURE__*/React.createElement("span", {
580        className: "btn-arrow"
581      }, /*#__PURE__*/React.createElement("svg", {
582        width: "10",
583        height: "10",
584        viewBox: "0 0 10 10",
585        fill: "none"
586      }, /*#__PURE__*/React.createElement("path", {
587        d: "M8 2L2 8M2 8H7M2 8V3",
588        stroke: "currentColor",
589        strokeWidth: "1.6",
590        strokeLinecap: "round"
591      }))), " Edit answers"), /*#__PURE__*/React.createElement("div", {
592        className: "spacer"
593      }), /*#__PURE__*/React.createElement("button", {
594        type: "button",
595        className: btnClass,
596        onClick: finalSubmit
597      }, props.submitLabel || 'Submit', " ", /*#__PURE__*/React.createElement(DFArr, null))));
598    }
599    return /*#__PURE__*/React.createElement("form", {
600      onSubmit: submit
601    }, props.heading ? /*#__PURE__*/React.createElement("h2", {
602      className: "ct-form-h",
603      dangerouslySetInnerHTML: {
604        __html: props.heading
605      }
606    }) : null, props.intro ? /*#__PURE__*/React.createElement("p", {
607      className: "ct-form-desc"
608    }, props.intro) : null, /*#__PURE__*/React.createElement("div", {
609      className: "ewa-hp",
610      "aria-hidden": "true"
611    }, /*#__PURE__*/React.createElement("label", null, "Leave this field empty", /*#__PURE__*/React.createElement("input", {
612      type: "text",
613      tabIndex: "-1",
614      autoComplete: "off",
615      value: hp,
616      onChange: e => setHp(e.target.value)
617    }))), /*#__PURE__*/React.createElement("div", {
618      className: "ap-fields"
619    }, (() => {
620      /* Row packing: half-width fields render side by side; a half with
621         no half NEXT to it (last, or followed by a full-width field)
622         stretches to 100% so no row is left with an empty column. */
623      const isHalf = f => {
624        if (isCheck(f.type) || isMulti(f.type) || f.type === 'radio' || f.type === 'file') return false;
625        const w = String(f.width == null ? '' : f.width);
626        if (w === '50' || w === '33' || w === 'half') return true;
627        return false;
628      };
629      const promote = {};
630      let pending = -1;
631      shown.forEach((f, si) => {
632        if (isHalf(f)) {
633          if (pending < 0) {
634            pending = si;
635          } else {
636            pending = -1;
637          }
638        } else if (pending >= 0) {
639          promote[pending] = true;
640          pending = -1;
641        }
642      });
643      if (pending >= 0) promote[pending] = true;
644      return shown.map((f, si) => {
645        const i = fields.indexOf(f);
646        const k = keyOf(f, i);
647        return /*#__PURE__*/React.createElement(Field, {
648          key: k,
649          f: f,
650          id: 'df-' + formId + '-' + k,
651          wrapId: 'dfw-' + formId + '-' + k,
652          value: form[k],
653          onChange: v => set(k, v),
654          err: errors[k] || '',
655          forceFull: !!promote[si]
656        });
657      });
658    })()), /*#__PURE__*/React.createElement("div", {
659      className: "ap-actions",
660      style: {
661        marginTop: 26
662      }
663    }, errCount > 0 ? /*#__PURE__*/React.createElement("span", {
664      className: "ap-err ap-err-block",
665      role: "alert"
666    }, "Please fix the ", errCount, " highlighted field", errCount > 1 ? 's' : '', " above.") : /*#__PURE__*/React.createElement("span", null), /*#__PURE__*/React.createElement("div", {
667      className: "spacer"
668    }), props.onChangeRole ? /*#__PURE__*/React.createElement("button", {
669      type: "button",
670      className: "btn btn-outline df-changerole",
671      onClick: props.onChangeRole
672    }, "Change role") : null, /*#__PURE__*/React.createElement("button", {
673      className: btnClass,
674      type: "submit"
675    }, props.reviewStep && !reviewing ? props.reviewLabel || 'Continue to Review' : props.submitLabel || 'Submit', " ", /*#__PURE__*/React.createElement(DFArr, null))));
676  }
677  window.DynamicForm = DynamicForm;
678})();
679})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.