PageSourceSearch

https://www.humanappeal.org.au/wp-content/themes/hello-elementor-child/assets/js/ha-v2-forms.js?ver=1790667077

js humanappeal.org.au collected 2026-10-02 16:28:05 UTC 37,146 bytes, 1,313 lines download raw bytes

1(function () {
2  var config = window.haData || {};
3  var errorIdCounter = 0;
4
5  function getFormAlert(form) {
6    var alerts = Array.prototype.slice.call(form.querySelectorAll('.ha-v2-form-alert'));
7
8    return alerts.find(function (alert) {
9      return !alert.closest('[hidden]');
10    }) || alerts[0] || null;
11  }
12
13  function ensureFormAlert(form) {
14    var alert = getFormAlert(form);
15
16    if (alert && !alert.closest('[hidden]')) {
17      return alert;
18    }
19
20    alert = form.querySelector('.ha-v2-form-alert[data-ha-fallback-alert]');
21
22    if (!alert) {
23      alert = document.createElement('div');
24      alert.className = 'ha-v2-form-alert';
25      alert.setAttribute('role', 'status');
26      alert.setAttribute('aria-live', 'polite');
27      alert.dataset.haFallbackAlert = 'true';
28
29      var progress = form.querySelector('[data-ha-form-progress]');
30      if (progress) {
31        progress.insertAdjacentElement('afterend', alert);
32      } else {
33        form.insertBefore(alert, form.firstChild);
34      }
35    }
36
37    return alert;
38  }
39
40  function clearAlerts(form) {
41    form.querySelectorAll('.ha-v2-form-alert').forEach(function (alert) {
42      alert.textContent = '';
43      alert.classList.remove('is-success', 'is-error', 'is-visible');
44    });
45  }
46
47  function setAlert(form, type, message) {
48    clearAlerts(form);
49
50    if (!message) {
51      return;
52    }
53
54    var alert = ensureFormAlert(form);
55
56    if (!alert) {
57      return;
58    }
59
60    alert.textContent = message;
61    alert.classList.add('is-visible', type === 'success' ? 'is-success' : 'is-error');
62
63    if (typeof alert.scrollIntoView === 'function') {
64      alert.scrollIntoView({ block: 'center', inline: 'nearest' });
65    }
66  }
67
68  function getPayloadData(payload) {
69    if (!payload || typeof payload.data === 'undefined') {
70      return {};
71    }
72
73    if (payload.data && typeof payload.data === 'object') {
74      return payload.data;
75    }
76
77    return {
78      message: String(payload.data || ''),
79    };
80  }
81
82  function getPayloadMessage(payload, data, fallback) {
83    return data.message || (payload && payload.message) || fallback;
84  }
85
86  function setSubmitting(form, isSubmitting) {
87    var submit = form.querySelector('[type="submit"]');
88
89    if (!submit) {
90      return;
91    }
92
93    if (!submit.dataset.defaultText) {
94      submit.dataset.defaultText = submit.textContent;
95    }
96
97    submit.disabled = isSubmitting;
98    submit.textContent = isSubmitting ? 'Sending...' : submit.dataset.defaultText;
99  }
100
101  function getAction(form) {
102    var actionField = form.querySelector('[name="action"]');
103    return form.dataset.action || (actionField ? actionField.value : '');
104  }
105
106  function getCustomSelectForNative(nativeSelect) {
107    var wrap = nativeSelect ? nativeSelect.closest('.ha-v2-select-wrap') : null;
108    return wrap ? wrap.querySelector('.ha-v2-custom-select') : null;
109  }
110
111  function getNativeSelectForCustom(customSelect) {
112    var id = customSelect ? customSelect.getAttribute('data-ha-v2-select-target') : '';
113    var wrap = customSelect ? customSelect.closest('.ha-v2-select-wrap') : null;
114
115    if (id) {
116      return document.getElementById(id);
117    }
118
119    return wrap ? wrap.querySelector('select') : null;
120  }
121
122  function getDropdownForCustomSelect(customSelect) {
123    var wrap = customSelect ? customSelect.closest('.ha-v2-select-wrap') : null;
124    return wrap ? wrap.querySelector('.ha-v2-dropdown') : null;
125  }
126
127  function getCustomSelectForDropdown(dropdown) {
128    var wrap = dropdown ? dropdown.closest('.ha-v2-select-wrap') : null;
129    return wrap ? wrap.querySelector('.ha-v2-custom-select') : null;
130  }
131
132  function getCustomSelectForTarget(target) {
133    if (!target || !target.matches) {
134      return null;
135    }
136
137    if (target.matches('.ha-v2-custom-select')) {
138      return target;
139    }
140
141    if (target.matches('select.ha-v2-native-select')) {
142      return getCustomSelectForNative(target);
143    }
144
145    if (target.matches('.ha-v2-select-wrap')) {
146      return target.querySelector('.ha-v2-custom-select');
147    }
148
149    return null;
150  }
151
152  function getCustomDropdownItems(dropdown) {
153    return dropdown ? Array.prototype.slice.call(dropdown.querySelectorAll('.ha-spo-dropdown-item')) : [];
154  }
155
156  function setCustomSelectInvalid(target, isInvalid, errorId) {
157    var customSelect = getCustomSelectForTarget(target);
158
159    if (!customSelect) {
160      return;
161    }
162
163    if (isInvalid) {
164      customSelect.setAttribute('aria-invalid', 'true');
165      if (errorId) {
166        addDescribedBy(customSelect, errorId);
167      }
168    } else {
169      customSelect.removeAttribute('aria-invalid');
170      if (errorId) {
171        removeDescribedBy(customSelect, errorId);
172      }
173    }
174  }
175
176  function syncCustomDropdownItemSemantics(dropdown) {
177    getCustomDropdownItems(dropdown).forEach(function (option) {
178      option.setAttribute('role', 'option');
179      option.setAttribute('tabindex', '-1');
180      option.setAttribute('aria-selected', option.classList.contains('selected') ? 'true' : 'false');
181    });
182  }
183
184  function setCustomSelectOpen(customSelect, isOpen) {
185    var dropdown = getDropdownForCustomSelect(customSelect);
186
187    if (!dropdown) {
188      return;
189    }
190
191    dropdown.classList.toggle('open', isOpen);
192    customSelect.classList.toggle('open', isOpen);
193    customSelect.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
194  }
195
196  function closeCustomSelects(exceptSelect) {
197    document.querySelectorAll('.ha-v2-custom-select.open').forEach(function (customSelect) {
198      if (exceptSelect && customSelect === exceptSelect) {
199        return;
200      }
201
202      setCustomSelectOpen(customSelect, false);
203    });
204  }
205
206  function focusCustomDropdownItem(dropdown, target) {
207    var items = getCustomDropdownItems(dropdown);
208    var item = null;
209
210    if (!items.length) {
211      return;
212    }
213
214    if (target === 'last') {
215      item = items[items.length - 1];
216    } else if (target === 'selected') {
217      item = dropdown.querySelector('.ha-spo-dropdown-item.selected') || items[0];
218    } else {
219      item = items[0];
220    }
221
222    item.focus();
223  }
224
225  function openCustomSelect(customSelect, focusTarget) {
226    closeCustomSelects(customSelect);
227    setCustomSelectOpen(customSelect, true);
228
229    if (focusTarget) {
230      focusCustomDropdownItem(getDropdownForCustomSelect(customSelect), focusTarget);
231    }
232  }
233
234  function moveCustomDropdownFocus(item, direction) {
235    var dropdown = item.closest('.ha-v2-dropdown');
236    var items = getCustomDropdownItems(dropdown);
237    var index = items.indexOf(item);
238
239    if (!items.length || index === -1) {
240      return;
241    }
242
243    items[(index + direction + items.length) % items.length].focus();
244  }
245
246  function syncCustomSelectFromNative(nativeSelect) {
247    var customSelect = getCustomSelectForNative(nativeSelect);
248    var dropdown = getDropdownForCustomSelect(customSelect);
249    var text = customSelect ? customSelect.querySelector('.ha-spo-select-text') : null;
250    var selectedOption = nativeSelect ? nativeSelect.options[nativeSelect.selectedIndex] : null;
251    var value = nativeSelect ? nativeSelect.value : '';
252    var label = selectedOption ? selectedOption.textContent.trim() : '';
253
254    if (!nativeSelect || !customSelect || !dropdown) {
255      return;
256    }
257
258    if (text) {
259      text.textContent = label;
260    }
261
262    getCustomDropdownItems(dropdown).forEach(function (option) {
263      var isSelected = option.getAttribute('data-value') === value;
264      option.classList.toggle('selected', isSelected);
265      option.setAttribute('aria-selected', isSelected ? 'true' : 'false');
266    });
267  }
268
269  function selectCustomDropdownItem(item, returnFocus) {
270    var dropdown = item.closest('.ha-v2-dropdown');
271    var customSelect = getCustomSelectForDropdown(dropdown);
272    var nativeSelect = getNativeSelectForCustom(customSelect);
273    var value = item.getAttribute('data-value');
274
275    if (!dropdown || !customSelect || !nativeSelect) {
276      return;
277    }
278
279    nativeSelect.value = value;
280    syncCustomSelectFromNative(nativeSelect);
281    setCustomSelectOpen(customSelect, false);
282
283    nativeSelect.dispatchEvent(new Event('input', { bubbles: true }));
284    nativeSelect.dispatchEvent(new Event('change', { bubbles: true }));
285
286    if (returnFocus !== false) {
287      customSelect.focus();
288    }
289  }
290
291  function initCustomSelects(form) {
292    form.querySelectorAll('.ha-v2-select-wrap').forEach(function (wrap) {
293      var nativeSelect = wrap.querySelector('select.ha-v2-native-select');
294      var customSelect = wrap.querySelector('.ha-v2-custom-select');
295      var dropdown = wrap.querySelector('.ha-v2-dropdown');
296
297      if (!nativeSelect || !customSelect || !dropdown || wrap.dataset.haV2SelectReady === 'true') {
298        return;
299      }
300
301      wrap.dataset.haV2SelectReady = 'true';
302      customSelect.setAttribute('aria-expanded', 'false');
303      syncCustomDropdownItemSemantics(dropdown);
304      syncCustomSelectFromNative(nativeSelect);
305
306      customSelect.addEventListener('click', function (event) {
307        event.stopPropagation();
308
309        if (customSelect.classList.contains('open')) {
310          setCustomSelectOpen(customSelect, false);
311        } else {
312          openCustomSelect(customSelect);
313        }
314      });
315
316      customSelect.addEventListener('keydown', function (event) {
317        if (event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar') {
318          event.preventDefault();
319          if (customSelect.classList.contains('open')) {
320            setCustomSelectOpen(customSelect, false);
321          } else {
322            openCustomSelect(customSelect, 'selected');
323          }
324        } else if (event.key === 'ArrowDown') {
325          event.preventDefault();
326          openCustomSelect(customSelect, 'selected');
327        } else if (event.key === 'ArrowUp') {
328          event.preventDefault();
329          openCustomSelect(customSelect, 'last');
330        } else if (event.key === 'Escape') {
331          setCustomSelectOpen(customSelect, false);
332        }
333      });
334
335      getCustomDropdownItems(dropdown).forEach(function (item) {
336        item.addEventListener('click', function (event) {
337          event.stopPropagation();
338          selectCustomDropdownItem(item);
339        });
340
341        item.addEventListener('keydown', function (event) {
342          if (event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar') {
343            event.preventDefault();
344            selectCustomDropdownItem(item);
345          } else if (event.key === 'ArrowDown') {
346            event.preventDefault();
347            moveCustomDropdownFocus(item, 1);
348          } else if (event.key === 'ArrowUp') {
349            event.preventDefault();
350            moveCustomDropdownFocus(item, -1);
351          } else if (event.key === 'Home') {
352            event.preventDefault();
353            focusCustomDropdownItem(dropdown, 'first');
354          } else if (event.key === 'End') {
355            event.preventDefault();
356            focusCustomDropdownItem(dropdown, 'last');
357          } else if (event.key === 'Escape') {
358            event.preventDefault();
359            setCustomSelectOpen(customSelect, false);
360            customSelect.focus();
361          }
362        });
363      });
364
365      nativeSelect.addEventListener('change', function () {
366        syncCustomSelectFromNative(nativeSelect);
367      });
368    });
369
370    form.addEventListener('reset', function () {
371      window.setTimeout(function () {
372        form.querySelectorAll('select.ha-v2-native-select').forEach(syncCustomSelectFromNative);
373        closeCustomSelects();
374      }, 0);
375    });
376
377    if (!initCustomSelects.hasDocumentHandler) {
378      document.addEventListener('click', function () {
379        closeCustomSelects();
380      });
381      initCustomSelects.hasDocumentHandler = true;
382    }
383  }
384
385  function initChoiceControls(form) {
386    form.querySelectorAll('.ha-v2-choice').forEach(function (choice) {
387      var input = choice.querySelector('input[type="checkbox"], input[type="radio"]');
388
389      if (!input || choice.dataset.haV2ChoiceReady === 'true') {
390        return;
391      }
392
393      choice.dataset.haV2ChoiceReady = 'true';
394
395      choice.addEventListener('click', function (event) {
396        if (event.target === input || input.disabled) {
397          return;
398        }
399
400        event.preventDefault();
401
402        if (input.type === 'radio') {
403          input.checked = true;
404        } else {
405          input.checked = !input.checked;
406        }
407
408        input.dispatchEvent(new Event('input', { bubbles: true }));
409        input.dispatchEvent(new Event('change', { bubbles: true }));
410      });
411    });
412  }
413
414  function clearInvalid(form) {
415    clearInlineErrors(form);
416
417    form.querySelectorAll('[aria-invalid="true"]').forEach(function (field) {
418      field.removeAttribute('aria-invalid');
419    });
420  }
421
422  function addDescribedBy(field, id) {
423    var ids = (field.getAttribute('aria-describedby') || '').split(/\s+/).filter(Boolean);
424
425    if (ids.indexOf(id) === -1) {
426      ids.push(id);
427    }
428
429    field.setAttribute('aria-describedby', ids.join(' '));
430  }
431
432  function removeDescribedBy(field, id) {
433    var ids = (field.getAttribute('aria-describedby') || '').split(/\s+/).filter(Boolean);
434    var nextIds = ids.filter(function (currentId) {
435      return currentId !== id && currentId.indexOf('ha-v2-error-') !== 0;
436    });
437
438    if (nextIds.length) {
439      field.setAttribute('aria-describedby', nextIds.join(' '));
440    } else {
441      field.removeAttribute('aria-describedby');
442    }
443  }
444
445  function getDescribedFields(target) {
446    if (!target) {
447      return [];
448    }
449
450    if (target.matches('input, select, textarea, .ha-v2-custom-select')) {
451      return [target];
452    }
453
454    return Array.prototype.slice.call(target.querySelectorAll('input, select, textarea, .ha-v2-custom-select'));
455  }
456
457  function clearInlineError(target) {
458    if (!target || !target.getAttribute) {
459      return;
460    }
461
462    var id = target.getAttribute('data-ha-error-id');
463
464    if (!id) {
465      return;
466    }
467
468    getDescribedFields(target).forEach(function (field) {
469      removeDescribedBy(field, id);
470    });
471
472    setCustomSelectInvalid(target, false, id);
473
474    var error = document.getElementById(id);
475    if (error) {
476      error.remove();
477    }
478
479    target.removeAttribute('data-ha-error-id');
480  }
481
482  function clearInlineErrors(scope) {
483    scope.querySelectorAll('[data-ha-error-id]').forEach(clearInlineError);
484    scope.querySelectorAll('[data-ha-field-error]').forEach(function (error) {
485      error.remove();
486    });
487  }
488
489  function getErrorAnchor(target) {
490    if (target.matches('select.ha-v2-native-select')) {
491      return target.closest('.ha-v2-select-wrap') || target;
492    }
493
494    if (target.matches('.ha-v2-select-wrap')) {
495      return target;
496    }
497
498    if (target.matches('input, select, textarea, [data-ha-required-group], .ha-v2-choice')) {
499      return target;
500    }
501
502    if (target.matches('.ha-v2-choice-field')) {
503      return target.querySelector('[data-ha-required-group], .ha-v2-choice-grid') || target;
504    }
505
506    return target;
507  }
508
509  function normalizeErrorMessage(message) {
510    if (Array.isArray(message)) {
511      return message.filter(Boolean).join(' ');
512    }
513
514    if (message && typeof message === 'object' && message.message) {
515      return String(message.message);
516    }
517
518    return String(message || 'Please check this field.');
519  }
520
521  function ensureInlineError(target) {
522    var id = target.getAttribute('data-ha-error-id');
523    var error = id ? document.getElementById(id) : null;
524
525    if (!id) {
526      errorIdCounter += 1;
527      id = 'ha-v2-error-' + errorIdCounter;
528      target.setAttribute('data-ha-error-id', id);
529    }
530
531    if (!error) {
532      error = document.createElement('p');
533      error.id = id;
534      error.className = 'ha-v2-field-error';
535      error.setAttribute('aria-live', 'polite');
536      error.dataset.haFieldError = 'true';
537
538      if (target.matches('.ha-v2-choice-field')) {
539        error.classList.add('ha-v2-field-error-choice');
540      }
541
542      getErrorAnchor(target).insertAdjacentElement('afterend', error);
543    }
544
545    return error;
546  }
547
548  function setInlineError(target, message) {
549    if (!target) {
550      return;
551    }
552
553    var error = ensureInlineError(target);
554
555    target.setAttribute('aria-invalid', 'true');
556    error.textContent = normalizeErrorMessage(message);
557    setCustomSelectInvalid(target, true, error.id);
558
559    getDescribedFields(target).forEach(function (field) {
560      addDescribedBy(field, error.id);
561    });
562  }
563
564  function clearRelatedInvalid(form, field) {
565    var name = field.getAttribute('name');
566    var names = [];
567    var choice = field.closest('.ha-v2-choice');
568    var choiceField = field.closest('.ha-v2-choice-field');
569    var requiredGroup = field.closest('[data-ha-required-group]');
570
571    field.removeAttribute('aria-invalid');
572    clearInlineError(field);
573    setCustomSelectInvalid(field, false);
574
575    if (choice) {
576      choice.removeAttribute('aria-invalid');
577      clearInlineError(choice);
578    }
579
580    if (name) {
581      names.push(name);
582
583      if (name.slice(-2) === '[]') {
584        names.push(name.slice(0, -2));
585      } else {
586        names.push(name + '[]');
587      }
588    }
589
590    names.forEach(function (fieldName) {
591      form.querySelectorAll('[name="' + CSS.escape(fieldName) + '"]').forEach(function (relatedField) {
592        relatedField.removeAttribute('aria-invalid');
593        clearInlineError(relatedField);
594
595        var relatedChoice = relatedField.closest('.ha-v2-choice');
596        if (relatedChoice) {
597          relatedChoice.removeAttribute('aria-invalid');
598          clearInlineError(relatedChoice);
599        }
600      });
601
602      form.querySelectorAll('[data-ha-required-group="' + CSS.escape(fieldName) + '"]').forEach(function (group) {
603        group.removeAttribute('aria-invalid');
604        clearInlineError(group);
605      });
606    });
607
608    if (requiredGroup) {
609      requiredGroup.removeAttribute('aria-invalid');
610      clearInlineError(requiredGroup);
611    }
612
613    if (choiceField) {
614      choiceField.removeAttribute('aria-invalid');
615      clearInlineError(choiceField);
616      choiceField.querySelectorAll('[aria-invalid="true"]').forEach(function (invalidField) {
617        invalidField.removeAttribute('aria-invalid');
618        clearInlineError(invalidField);
619      });
620    }
621  }
622
623  function initInvalidStateClearing(form) {
624    function handleFieldChange(event) {
625      var field = event.target;
626
627      if (!field || !field.matches('input, select, textarea')) {
628        return;
629      }
630
631      clearRelatedInvalid(form, field);
632
633      if (!form.querySelector('[aria-invalid="true"]')) {
634        clearAlerts(form);
635      }
636    }
637
638    form.addEventListener('input', handleFieldChange);
639    form.addEventListener('change', handleFieldChange);
640  }
641
642  function findErrorTarget(form, name) {
643    var escapedName = CSS.escape(name);
644    var group = form.querySelector('[data-ha-required-group="' + escapedName + '"]');
645    var field = form.querySelector('[name="' + escapedName + '"]');
646
647    if (!field && name.slice(-2) === '[]') {
648      field = form.querySelector('[name="' + CSS.escape(name.slice(0, -2)) + '"]');
649    }
650
651    if (!field && name.slice(-2) !== '[]') {
652      field = form.querySelector('[name="' + CSS.escape(name + '[]') + '"]');
653    }
654
655    return group || field || null;
656  }
657
658  function markInvalid(form, errors) {
659    var firstInvalid = null;
660
661    Object.keys(errors || {}
661).forEach(function (name) {
662      var target = findErrorTarget(form, name);
663
664      if (!firstInvalid && target) {
665        firstInvalid = target;
666      }
667
668      if (target) {
669        setInlineError(target, errors[name]);
670      }
671    });
672
673    return firstInvalid;
674  }
675
676  function getValidationMessage(field) {
677    var validity = field.validity || {};
678
679    if (validity.valueMissing) {
680      if (field.type === 'file') {
681        return 'Upload a file.';
682      }
683
684      if (field.type === 'checkbox') {
685        return 'This checkbox is required.';
686      }
687
688      if (field.tagName === 'SELECT') {
689        return 'Select an option.';
690      }
691
692      return 'This field is required.';
693    }
694
695    if (validity.typeMismatch && field.type === 'email') {
696      return 'Enter a valid email address.';
697    }
698
699    if (validity.rangeUnderflow && field.min) {
700      return 'Minimum ' + field.min + '.';
701    }
702
703    if (validity.rangeOverflow && field.max) {
704      return 'Maximum ' + field.max + '.';
705    }
706
707    if (validity.stepMismatch) {
708      return field.step === '1' ? 'Use a whole number.' : 'Enter a valid value.';
709    }
710
711    if (validity.tooShort && field.minLength > 0) {
712      return 'Use at least ' + field.minLength + ' characters.';
713    }
714
715    if (validity.tooLong && field.maxLength > 0) {
716      return 'Use no more than ' + field.maxLength + ' characters.';
717    }
718
719    if (validity.patternMismatch) {
720      return field.title || 'Enter a valid value.';
721    }
722
723    return field.validationMessage || 'Please check this field.';
724  }
725
726  function formatFileSize(size) {
727    if (!size) {
728      return '0 KB';
729    }
730
731    if (size < 1024 * 1024) {
732      return Math.max(1, Math.round(size / 1024)) + ' KB';
733    }
734
735    return (size / (1024 * 1024)).toFixed(1).replace(/\.0$/, '') + ' MB';
736  }
737
738  function getFileTypeLabel(file) {
739    var extension = (file.name.split('.').pop() || '').toUpperCase();
740
741    if (extension) {
742      return extension + ' selected';
743    }
744
745    return 'File selected';
746  }
747
748  function setPreviewLinkHref(link, url) {
749    if (!link) {
750      return;
751    }
752    if (url) {
753      link.setAttribute('href', url);
754    } else {
755      link.removeAttribute('href');
756    }
757  }
758
759  function clearFilePreview(preview) {
760    var image = preview.querySelector('[data-ha-file-preview-image]');
761    var icon = preview.querySelector('.ha-v2-upload-preview-icon');
762    var link = preview.querySelector('[data-ha-file-preview-link]');
763    var name = preview.querySelector('[data-ha-file-preview-name]');
764    var detail = preview.querySelector('[data-ha-file-preview-detail]');
765    var url = preview.dataset.haPreviewUrl;
766
767    if (url && window.URL && typeof window.URL.revokeObjectURL === 'function') {
768      window.URL.revokeObjectURL(url);
769    }
770
771    delete preview.dataset.haPreviewUrl;
772    preview.hidden = true;
773
774    if (image) {
775      image.onload = null;
776      image.onerror = null;
777      image.hidden = true;
778      image.removeAttribute('src');
779    }
780
781    if (icon) {
782      setHidden(icon, true);
783    }
784
785    if (link) {
786      setPreviewLinkHref(link, '');
787      link.removeAttribute('aria-label');
788    }
789
790    if (name) {
791      name.textContent = '';
792    }
793
794    if (detail) {
795      detail.textContent = '';
796    }
797  }
798
799    function setHidden(el, hidden) {
800    if (!el) {
801      return;
802    }
803    if (hidden) {
804      el.setAttribute('hidden', '');
805    } else {
806      el.removeAttribute('hidden');
807    }
808  }
809
810  function canPreviewFile(file) {
811    return /^image\//.test(file.type) || /\.(jpe?g|png|gif|webp)$/i.test(file.name);
812  }
813
814  function updateFilePreview(input, preview) {
815    var file = input.files && input.files[0];
816    var image = preview.querySelector('[data-ha-file-preview-image]');
817    var icon = preview.querySelector('.ha-v2-upload-preview-icon');
818    var link = preview.querySelector('[data-ha-file-preview-link]');
819    var name = preview.querySelector('[data-ha-file-preview-name]');
820    var detail = preview.querySelector('[data-ha-file-preview-detail]');
821
822    clearFilePreview(preview);
823
824    if (!file) {
825      return;
826    }
827
828    preview.hidden = false;
829
830    if (name) {
831      name.textContent = file.name;
832    }
833
834    if (detail) {
835      detail.textContent = getFileTypeLabel(file) + ' - ' + formatFileSize(file.size);
836    }
837
838    if (link) {
839      link.setAttribute('aria-label', 'Preview ' + file.name + ' in a new tab');
840    }
841
842    if (!window.URL || typeof window.URL.createObjectURL !== 'function') {
843      if (icon) {
844        setHidden(icon, false);
845      }
846      return;
847    }
848
849    var url = window.URL.createObjectURL(file);
850    preview.dataset.haPreviewUrl = url;
851    setPreviewLinkHref(link, url);
852
853    if (!canPreviewFile(file)) {
854      if (icon) {
855        setHidden(icon, false);
856      }
857      return;
858    }
859
860    if (!image) {
861      return;
862    }
863
864    image.hidden = true;
865
866    image.onload = function () {
867      image.hidden = false;
868      if (icon) {
869        setHidden(icon, true);
870      }
871    };
872
873    image.onerror = function () {
874      image.hidden = true;
875      image.removeAttribute('src');
876      if (icon) {
877        setHidden(icon, false);
878      }
879      detail.textContent = getFileTypeLabel(file) + ' - ' + formatFileSize(file.size);
880    };
881
882    image.src = url;
883  }
884
885  function initFilePreviews(form) {
886    var previews = Array.prototype.slice.call(form.querySelectorAll('[data-ha-file-preview]'));
887
888    if (!previews.length) {
889      return;
890    }
891
892    previews.forEach(function (preview) {
893      var field = preview.closest('.ha-v2-field');
894      var input = field ? field.querySelector('input[type="file"]') : null;
895
896      if (!input) {
897        return;
898      }
899
900      input.addEventListener('change', function () {
901        updateFilePreview(input, preview);
902      });
903    });
904
905    function clearAllPreviews() {
906      previews.forEach(clearFilePreview);
907    }
908
909    form.addEventListener('reset', function () {
910      window.setTimeout(clearAllPreviews, 0);
911    });
912
913    form.addEventListener('ha:v2-form-success', clearAllPreviews);
914  }
915
916  function submitFormWithToken(form, token) {
917    var formData = new FormData(form);
918    formData.set('action', getAction(form));
919    formData.set('nonce', config.nonce || formData.get('nonce') || '');
920    if (token) {
921      formData.set('g-recaptcha-response', token);
922    }
923
924    fetch(config.ajaxUrl || form.action, {
925      method: 'POST',
926      body: formData,
927      credentials: 'same-origin',
928    })
929      .then(function (response) {
930        return response.json().catch(function () {
931          return {
932            success: false,
933            data: {
934              message: 'The form could not be submitted. Please try again.',
935            },
936          };
937        });
938      })
939      .then(function (payload) {
940        var data = getPayloadData(payload);
941
942        if (!payload || !payload.success) {
943          var firstInvalid = markInvalid(form, data.errors || {});
944          form.dispatchEvent(new CustomEvent('ha:v2-form-errors', {
945            detail: {
946              errors: data.errors || {},
947              firstInvalid: firstInvalid,
948            },
949          }));
950          setAlert(form, 'error', getPayloadMessage(payload, data, 'Please check the highlighted fields.'));
951          return;
952        }
953
954        form.reset();
955        form.dispatchEvent(new CustomEvent('ha:v2-form-success'));
956        setAlert(form, 'success', getPayloadMessage(payload, data, 'Your submission was successful.'));
957      })
958      .catch(function () {
959        setAlert(form, 'error', 'The form could not be submitted. Please try again.');
960      })
961      .finally(function () {
962        setSubmitting(form, false);
963      });
964  }
965
966  function initAjaxForm(form) {
967    form.addEventListener('submit', function (event) {
968      event.preventDefault();
969      clearInvalid(form);
970      setAlert(form, '', '');
971
972      if (config.recaptchaSiteKey && typeof grecaptcha === 'undefined') {
973        setAlert(form, 'error', 'reCAPTCHA could not be loaded. Please try again.');
974        return;
975      }
976
977      if (typeof grecaptcha !== 'undefined' && config.recaptchaSiteKey) {
978        setSubmitting(form, true);
979        var onRecaptchaError = function () {
980          setAlert(form, 'error', 'reCAPTCHA verification could not be executed. Please try again.');
981          setSubmitting(form, false);
982        };
983        grecaptcha.ready(function() {
984          // execute() throws synchronously for an invalid site key.
985          try {
986            grecaptcha.execute(config.recaptchaSiteKey, {action: 'submit'}).then(function(token) {
987              submitFormWithToken(form, token);
988            }).catch(onRecaptchaError);
989          } catch (error) {
990            onRecaptchaError();
991          }
992        });
993      } else {
994        setSubmitting(form, true);
995        submitFormWithToken(form, '');
996      }
997    });
998  }
999
1000  function isVisibleField(field) {
1001    if (field.matches('select.ha-v2-native-select')) {
1002      return !field.disabled;
1003    }
1004
1005    return !field.disabled && field.type !== 'hidden' && field.offsetParent !== null;
1006  }
1007
1008  function validateStep(step) {
1009    var valid = true;
1010    var radioNames = {};
1011    var fields = step.querySelectorAll('input, select, textarea');
1012    var firstInvalid = null;
1013
1014    clearInvalid(step);
1015
1016    function invalidate(target, message) {
1017      valid = false;
1018
1019      if (!firstInvalid) {
1020        firstInvalid = target;
1021      }
1022
1023      setInlineError(target, message);
1024    }
1025
1026    step.querySelectorAll('[data-ha-required-group]').forEach(function (group) {
1027      group.removeAttribute('aria-invalid');
1028
1029      if (!group.querySelector('input:checked')) {
1030        invalidate(group, 'Choose at least one option.');
1031      }
1032    });
1033
1034    fields.forEach(function (field) {
1035      if (!isVisibleField(field)) {
1036        return;
1037      }
1038
1039      field.removeAttribute('aria-invalid');
1040
1041      if (!field.required) {
1042        return;
1043      }
1044
1045      if (field.type === 'radio') {
1046        if (radioNames[field.name]) {
1047          return;
1048        }
1049        radioNames[field.name] = true;
1050        if (!step.querySelector('input[name="' + CSS.escape(field.name) + '"]:checked')) {
1051          invalidate(field.closest('.ha-v2-choice-field') || field, 'Select an option.');
1052        }
1053        return;
1054      }
1055
1056      if (field.type === 'checkbox' && !field.checked) {
1057        invalidate(field.closest('.ha-v2-choice') || field, getValidationMessage(field));
1058        return;
1059      }
1060
1061      if (field.type === 'file' && !field.files.length) {
1062        invalidate(field, getValidationMessage(field));
1063        return;
1064      }
1065
1066      if (typeof field.checkValidity === 'function') {
1067        if (!field.checkValidity()) {
1068          invalidate(field, getValidationMessage(field));
1069        }
1070        return;
1071      }
1072
1073      if (!field.value) {
1074        invalidate(field, 'This field is required.');
1075      }
1076    });
1077
1078    if (firstInvalid) {
1079      step.dispatchEvent(new CustomEvent('ha:v2-step-invalid', {
1080        bubbles: true,
1081        detail: {
1082          firstInvalid: firstInvalid,
1083        },
1084      }));
1085    }
1086
1087    return valid;
1088  }
1089
1090  function initSteps(form) {
1091    var steps = Array.prototype.slice.call(form.querySelectorAll('[data-ha-form-step]'));
1092    var progress = form.querySelector('[data-ha-form-progress]');
1093    var progressItems = [];
1094    var current = 0;
1095
1096    if (!steps.length) {
1097      return;
1098    }
1099
1100    function getStepLabel(step, index) {
1101      var legend = step.querySelector('.ha-v2-step-head legend, legend');
1102      var label = legend ? legend.textContent.trim() : '';
1103
1104      return label || 'Step ' + (index + 1);
1105    }
1106
1107    if (progress) {
1108      progress.textContent = '';
1109
1110      if (progress.tagName.toLowerCase() !== 'ol' && progress.tagName.toLowerCase() !== 'ul') {
1111        progress.setAttribute('role', 'list');
1112      }
1113
1114      steps.forEach(function (step, index) {
1115        var item = document.createElement('li');
1116        var number = document.createElement('span');
1117        var label = document.createElement('span');
1118        var labelText = getStepLabel(step, index);
1119
1120        item.className = 'ha-v2-progress-step';
1121        number.className = 'ha-v2-progress-number';
1122        label.className = 'ha-v2-progress-label';
1123        item.setAttribute('aria-label', 'Step ' + (index + 1) + ': ' + labelText);
1124        number.setAttribute('aria-hidden', 'true');
1125        number.textContent = index + 1;
1126        label.textContent = labelText;
1127
1128        item.appendChild(number);
1129        item.appendChild(label);
1130        progress.appendChild(item);
1131        progressItems.push(item);
1132      });
1133    }
1134
1135    function render() {
1136      steps.forEach(function (step, index) {
1137        step.hidden = index !== current;
1138      });
1139
1140      progressItems.forEach(function (item, index) {
1141        item.classList.toggle('is-active', index === current);
1142        item.classList.toggle('is-complete', index < current);
1143
1144        if (index === current) {
1145          item.setAttribute('aria-current', 'step');
1146        } else {
1147          item.removeAttribute('aria-current');
1148        }
1149      });
1150    }
1151
1152    function getStickyTopOffset() {
1153      var selectors = ['#wpadminbar', '#ha-header', '.ha-header', '#site-header', '.site-header'];
1154      var offset = 0;
1155
1156      selectors.forEach(function (selector) {
1157        var element = document.querySelector(selector);
1158
1159        if (!element || typeof element.getBoundingClientRect !== 'function') {
1160          return;
1161        }
1162
1163        var style = window.getComputedStyle ? window.getComputedStyle(element) : null;
1164        var position = style ? style.position : '';
1165
1166        if (position !== 'fixed' && position !== 'sticky') {
1167          return;
1168        }
1169
1170        var rect = element.getBoundingClientRect();
1171
1172        if (rect.height > 0 && rect.bottom > 0 && rect.top <= 0) {
1173          offset = Math.max(offset, rect.bottom);
1174        }
1175      });
1176
1177      return offset;
1178    }
1179
1180    function scrollToFormTop() {
1181      if (typeof window.scrollTo !== 'function' || typeof form.getBoundingClientRect !== 'function') {
1182        return;
1183      }
1184
1185      var formStyle = window.getComputedStyle ? window.getComputedStyle(form) : null;
1186      var scrollGap = formStyle ? parseFloat(formStyle.getPropertyValue('--space-4')) : 0;
1187      var pageOffset = window.pageYOffset || document.documentElement.scrollTop || 0;
1188      var reducedMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
1189      var target = Math.max(0, form.getBoundingClientRect().top + pageOffset - getStickyTopOffset() - (scrollGap || 16));
1190      var schedule = window.requestAnimationFrame || function (callback) {
1191        return window.setTimeout(callback, 0);
1192      };
1193
1194      schedule(function () {
1195        try {
1196          window.scrollTo({
1197            top: target,
1198            behavior: reducedMotion ? 'auto' : 'smooth',
1199          });
1200        } catch (error) {
1201          window.scrollTo(0, target);
1202        }
1203      });
1204    }
1205
1206    function getFocusableInvalid(target) {
1207      if (!target) {
1208        return null;
1209      }
1210
1211      if (target.matches('select.ha-v2-native-select')) {
1212        return getCustomSelectForNative(target) || target;
1213      }
1214
1215      if (target.matches('input, select, textarea, .ha-v2-custom-select')) {
1216        return target;
1217      }
1218
1219      return target.querySelector('.ha-v2-custom-select, input, select, textarea');
1220    }
1221
1222    function focusInvalid(target) {
1223      var field = getFocusableInvalid(target);
1224
1225      if (field && !field.disabled && typeof field.focus === 'function') {
1226        try {
1227          field.focus({ preventScroll: true });
1228        } catch (error) {
1229          field.focus();
1230        }
1231      }
1232
1233      if (target && typeof target.scrollIntoView === 'function') {
1234        target.scrollIntoView({ block: 'center', inline: 'nearest' });
1235      }
1236    }
1237
1238    function goToInvalid(target) {
1239      var step = target && target.closest('[data-ha-form-step]');
1240      var stepIndex = step ? steps.indexOf(step) : -1;
1241
1242      if (stepIndex >= 0) {
1243        current = stepIndex;
1244        render();
1245      }
1246
1247      window.setTimeout(function () {
1248        focusInvalid(target);
1249      }, 0);
1250    }
1251
1252    form.querySelectorAll('[data-ha-next-step]').forEach(function (button) {
1253      button.addEventListener('click', function () {
1254        if (!validateStep(steps[current])) {
1255          setAlert(form, 'error', 'Please complete the required fields before continuing.');
1256          return;
1257        }
1258        setAlert(form, '', '');
1259        current = Math.min(current + 1, steps.length - 1);
1260        render();
1261        scrollToFormTop();
1262      });
1263    });
1264
1265    form.querySelectorAll('[data-ha-prev-step]').forEach(function (button) {
1266      button.addEventListener('click', function () {
1267        setAlert(form, '', '');
1268        current = Math.max(current - 1, 0);
1269        render();
1270        scrollToFormTop();
1271      });
1272    });
1273
1274    form.addEventListener('submit', function (event) {
1275      if (!validateStep(steps[current])) {
1276        event.preventDefault();
1277        setAlert(form, 'error', 'Please complete the required fields before submitting.');
1278      }
1279    }, true);
1280
1281    form.addEventListener('ha:v2-step-invalid', function (event) {
1282      if (event.detail && event.detail.firstInvalid) {
1283        window.setTimeout(function () {
1284          focusInvalid(event.detail.firstInvalid);
1285        }, 0);
1286      }
1287    });
1288
1289    form.addEventListener('ha:v2-form-errors', function (event) {
1290      if (event.detail && event.detail.firstInvalid) {
1291        goToInvalid(event.detail.firstInvalid);
1292      }
1293    });
1294
1295    form.addEventListener('ha:v2-form-success', function () {
1296      current = 0;
1297      render();
1298    });
1299
1300    render();
1301  }
1302
1303  document.addEventListener('DOMContentLoaded', function () {
1304    document.querySelectorAll('[data-ha-native-form]').forEach(function (form) {
1305      initCustomSelects(form);
1306      initChoiceControls(form);
1307      initFilePreviews(form);
1308      initInvalidStateClearing(form);
1309      initSteps(form);
1310      initAjaxForm(form);
1311    });
1312  });
1313})();

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.