# contact-forms/trunk/assets/js/admin/form-settings.js

Contact Forms by Cimatti, version trunk. 835 lines.

- Page: https://pluginprobe.com/plugins/contact-forms/trunk/code/assets/js/admin/form-settings.js
- Raw: https://pluginprobe.com/plugins/contact-forms/trunk/raw/assets/js/admin/form-settings.js
- Modified: 2026-09-02T14:00:10+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/contact-forms/trunk/code/assets/js/admin/form-settings.js#L10-L20`.

```javascript
/**
 * Contact Forms - Form Settings Editor
 * 
 * Handles real-time preview updates and form settings saving.
 */
jQuery(function($){
  'use strict';

  // ========================================================================
  // Form Editor Page Initialization
  // ========================================================================

  // Message override radio buttons - toggle WP editor visibility
  $('input[type=radio]').on('change', function() {
    var name = $(this).attr('name');
    if ($(this).val() === '1') {
      $('#' + name + ' .defalut_message').hide();
      $('#' + name + ' .wp-editor-wrap').show();
    } else {
      if ($(this).val() !== '-1') {
        $('#' + name + ' .defalut_message').show();
      } else {
        $('#' + name + ' .defalut_message').hide();
      }
      $('#' + name + ' .wp-editor-wrap').hide();
    }
  });

  // Initialize message field visibility
  $.each(['success_message', 'error_message', 'admin_emails_message', 'confirmation_emails_message'], function(i, key) {
    var value = $('#accua_form_' + key + ' .accua_form_check_override:checked').val();
    if (value !== undefined && value != 0) {
      if (value != -1) {
        $('#accua_form_' + key + ' .wp-editor-wrap').show();
      } else {
        $('#accua_form_' + key + ' .wp-editor-wrap').hide();
      }
      $('#accua_form_' + key + ' .defalut_message').hide();
    } else {
      $('#accua_form_' + key + ' .wp-editor-wrap').hide();
      $('#accua_form_' + key + ' .defalut_message').show();
    }
  });

  // Retention override toggle
  $('#accua_form_retention_override').on('change', function() {
    $('#accua_form_retention_fields').toggle(this.checked);
  });

  // Preview area - resizable and auto-resize iframe on load
  var $previewWrapper = $('#accua_form_preview_area_wrapper');
  var $previewIframe = $('#accua_form_preview_area');
  var previewWrapperResizable = false;
  if ($previewWrapper.length && $.fn.resizable) {
    $previewWrapper.resizable({ handles: 's' });
    $previewIframe.css({ width: '100%', height: '100%' });
    previewWrapperResizable = true;
  }

  /**
   * The iframe src is rendered server-side, so on a fast response the load
   * event may fire before any handler in this file is attached. The initial
   * about:blank document also reports readyState 'complete' but has an empty
   * body; require a non-empty body to tell the real preview apart.
   */
  function previewFrameAlreadyLoaded() {
    if (!$previewIframe.length) return false;
    try {
      var doc = $previewIframe[0].contentDocument;
      return !!(doc && doc.readyState === 'complete' && doc.body && doc.body.children.length);
    } catch (e) {
      return false; // cross-origin
    }
  }

  function resizePreviewToContent() {
    try {
      var h = $previewIframe[0].contentWindow.document.documentElement.scrollHeight + 200;
      if (previewWrapperResizable) {
        // The iframe fills the wrapper (width/height 100%) and jQuery UI
        // resizable manages the wrapper's height, so size the wrapper;
        // a pixel height on the iframe itself would detach it from manual
        // wrapper resizing.
        $previewWrapper.height(h);
      } else {
        $previewIframe[0].style.height = h + 'px';
      }
    } catch (e) { /* cross-origin */ }
  }

  if ($previewIframe.length) {
    $previewIframe.on('load', resizePreviewToContent);
    if (previewFrameAlreadyLoaded()) {
      resizePreviewToContent();
    }
  }

  // ========================================================================
  // Configuration
  // ========================================================================
  var DEBOUNCE_DELAY = 100; // ms delay for debounced updates
  var PREVIEW_LOAD_DELAY = 300; // ms delay after iframe load before updating

  // All style fields that support override checkbox + value
  var ALL_STYLE_FIELDS = {
    // Form container styles
    form: [
      'style_margin',
      'style_border_color',
      'style_border_width', 
      'style_border_radius',
      'style_background_color',
      'style_padding',
      'style_color',
      'style_font_size'
    ],
    // Field styles
    field: [
      'style_field_spacing',
      'style_field_border_color',
      'style_field_border_width',
      'style_field_border_radius',
      'style_field_background_color',
      'style_field_padding',
      'style_field_color'
    ],
    // Submit button styles
    submit: [
      'style_submit_border_color',
      'style_submit_border_width',
      'style_submit_border_radius',
      'style_submit_background_color',
      'style_submit_padding',
      'style_submit_color',
      'style_submit_font_size'
    ]
  };

  // Color fields that use wpColorPicker
  var COLOR_FIELDS = [
    'style_border_color',
    'style_background_color', 
    'style_color',
    'style_field_border_color',
    'style_field_background_color',
    'style_field_color',
    'style_submit_border_color',
    'style_submit_background_color',
    'style_submit_color'
  ];

  // ========================================================================
  // State
  // ========================================================================
  var previewReady = false;
  var $saveButton = $('.accua_form_save_settings_button');

  // ========================================================================
  // Utility Functions
  // ========================================================================
  
  /**
   * Simple debounce function
   */
  function debounce(func, wait) {
    var timeout;
    return function() {
      var context = this, args = arguments;
      clearTimeout(timeout);
      timeout = setTimeout(function() {
        func.apply(context, args);
      }, wait);
    };
  }

  /**
   * Get TinyMCE content or fallback to textarea value
   */
  function getTinyMCEContent(name) {
    if ($('#' + name + ' .wp-editor-wrap').hasClass('tmce-active') && typeof tinyMCE !== 'undefined') {
      var editor = tinyMCE.get(name + '_textarea');
      return editor ? editor.getContent() : $('#' + name + '_textarea').val();
    }
    return $('#' + name + '_textarea').val();
  }

  /**
   * Parse a numeric value and add px suffix if needed
   */
  function parseStyleValue(value) {
    if (!value || value === '') return '';
    value = String(value).trim();
    // If it's a pure number, add px
    if (/^-?\d+(\.\d+)?$/.test(value)) {
      return value + 'px';
    }
    return value;
  }

  // ========================================================================
  // Preview Document Access
  // ========================================================================
  
  /**
   * Safely get the preview iframe document
   */
  function getPreviewDocument() {
    var iframe = document.getElementById('accua_form_preview_area');
    if (!iframe) return null;
    try {
      var doc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document);
      return doc && doc.body ? doc : null;
    } catch (e) {
      return null;
    }
  }

  /**
   * Get the form element from preview
   */
  function getPreviewForm() {
    var doc = getPreviewDocument();
    return doc ? doc.querySelector('form.accua-form') : null;
  }

  // ========================================================================
  // Style Value Retrieval
  // ========================================================================
  
  /**
   * Get style value if the override checkbox is checked
   */
  function getStyleValue(key) {
    var $container = $('#accua_form_' + key);
    var $checkbox = $container.find('.accua_form_check_override');
    
    // Check if override is enabled
    if (!$checkbox.length || !$checkbox.is(':checked')) {
      return '';
    }
    
    // For color pickers, get value from the hidden input (wpColorPicker syncs to it)
    var $valueInput = $container.find('.accua_form_value');
    return $valueInput.val() || '';
  }

  // ========================================================================
  // Layout Preview
  // ========================================================================
  
  /**
   * Reload preview iframe with new layout parameter.
   * Layout changes require HTML structure rebuild, not just CSS class toggling.
   * Different layouts (inline, toplabel, sidebyside) generate different HTML.
   */
  function reloadPreviewWithLayout(layout) {
    var formId = $('#accua_form_save_settings_id').val();
    var $iframe = $('#accua_form_preview_area');
    var $wrapper = $('#accua_form_preview_area_wrapper');
    
    previewReady = false;
    
    // Show loading state
    $wrapper.addClass('accua-form-preview-loading');
    
    // Build preview URL with layout override parameter
    var previewUrl = 'admin-ajax.php?action=accua_forms_preview&fid=' + formId + 
                     '&_wpnonce=' + accua_forms_nonces.preview_nonce;
    
    // Always pass layout parameter - use 'default' when empty to signal global default should be used
    var layoutParam = (layout && layout !== '') ? layout : 'default';
    previewUrl += '&preview_layout=' + encodeURIComponent(layoutParam);
    
    $iframe[0].src = previewUrl;
  }

  /**
   * Update preview layout class based on dropdown selection
   * NOTE: This only toggles CSS classes - used for CSS-only style changes.
   * For actual layout changes, use reloadPreviewWithLayout() instead.
   */
  function updatePreviewLayout() {
    var form = getPreviewForm();
    if (!form) return;

    var layout = $('#accua_form_layout .accua_form_value').val() || '';

    // Remove all layout classes
    form.classList.remove(
      'accua-form-view-standard',
      'accua-form-view-sidebyside', 
      'accua-form-view-inlinelabel'
    );

    // Add appropriate class based on selection
    switch (layout) {
      case 'toplabel':
        form.classList.add('accua-form-view-standard');
        break;
      case 'inlinelabel':
        form.classList.add('accua-form-view-inlinelabel');
        break;
      case 'sidebyside':
        form.classList.add('accua-form-view-sidebyside');
        break;
      default:
        // Empty/default - use sidebyside as fallback for backwards compatibility
        form.classList.add('accua-form-view-sidebyside');
        break;
    }
  }

  // ========================================================================
  // Styles Preview
  // ========================================================================
  
  /**
   * Build inline style string from style object
   */
  function buildStyleString(styles) {
    var parts = [];
    for (var prop in styles) {
      if (styles.hasOwnProperty(prop) && styles[prop]) {
        parts.push(prop + ':' + styles[prop]);
      }
    }
    return parts.join(';');
  }

  /**
   * Update all preview styles
   */
  function updatePreviewStyles() {
    var form = getPreviewForm();
    if (!form) return;

    var doc = getPreviewDocument();
    if (!doc) return;

    // ---- Form Container Styles ----
    var formStyles = {};
    
    var margin = getStyleValue('style_margin');
    if (margin) formStyles['margin'] = parseStyleValue(margin);
    
    var borderColor = getStyleValue('style_border_color');
    if (borderColor) formStyles['border-color'] = borderColor;
    
    var borderWidth = getStyleValue('style_border_width');
    if (borderWidth) {
      formStyles['border-width'] = parseStyleValue(borderWidth);
      formStyles['border-style'] = 'solid';
    }
    
    var borderRadius = getStyleValue('style_border_radius');
    if (borderRadius) formStyles['border-radius'] = parseStyleValue(borderRadius);
    
    var backgroundColor = getStyleValue('style_background_color');
    if (backgroundColor) formStyles['background-color'] = backgroundColor;
    
    var padding = getStyleValue('style_padding');
    if (padding) formStyles['padding'] = parseStyleValue(padding);
    
    var color = getStyleValue('style_color');
    if (color) formStyles['color'] = color;
    
    var fontSize = getStyleValue('style_font_size');
    if (fontSize) formStyles['font-size'] = parseStyleValue(fontSize);

    form.style.cssText = buildStyleString(formStyles);

    // ---- Field Styles ----
    var fieldStyles = {};
    
    var fieldBorderColor = getStyleValue('style_field_border_color');
    if (fieldBorderColor) fieldStyles['border-color'] = fieldBorderColor;
    
    var fieldBorderWidth = getStyleValue('style_field_border_width');
    if (fieldBorderWidth) {
      fieldStyles['border-width'] = parseStyleValue(fieldBorderWidth);
      fieldStyles['border-style'] = 'solid';
    }
    
    var fieldBorderRadius = getStyleValue('style_field_border_radius');
    if (fieldBorderRadius) fieldStyles['border-radius'] = parseStyleValue(fieldBorderRadius);
    
    var fieldBackgroundColor = getStyleValue('style_field_background_color');
    if (fieldBackgroundColor) fieldStyles['background-color'] = fieldBackgroundColor;
    
    var fieldPadding = getStyleValue('style_field_padding');
    if (fieldPadding) fieldStyles['padding'] = parseStyleValue(fieldPadding);
    
    var fieldColor = getStyleValue('style_field_color');
    if (fieldColor) fieldStyles['color'] = fieldColor;

    var fieldStyleString = buildStyleString(fieldStyles);
    var fields = doc.querySelectorAll('.pfbc-textbox, .pfbc-textarea, .pfbc-select, input[type="text"], input[type="email"], input[type="date"], input[type="password"], textarea, select');
    for (var i = 0; i < fields.length; i++) {
      fields[i].style.cssText = fieldStyleString;
    }

    // Field spacing (margin-bottom on pfbc-element)
    var fieldSpacing = getStyleValue('style_field_spacing');
    if (fieldSpacing) {
      var elements = doc.querySelectorAll('.pfbc-element');
      var spacingValue = parseStyleValue(fieldSpacing);
      for (var j = 0; j < elements.length; j++) {
        elements[j].style.marginBottom = spacingValue;
      }
    }

    // ---- Submit Button Styles ----
    var submitStyles = {};
    
    var submitBorderColor = getStyleValue('style_submit_border_color');
    if (submitBorderColor) submitStyles['border-color'] = submitBorderColor;
    
    var submitBorderWidth = getStyleValue('style_submit_border_width');
    if (submitBorderWidth) {
      submitStyles['border-width'] = parseStyleValue(submitBorderWidth);
      submitStyles['border-style'] = 'solid';
    }
    
    var submitBorderRadius = getStyleValue('style_submit_border_radius');
    if (submitBorderRadius) submitStyles['border-radius'] = parseStyleValue(submitBorderRadius);
    
    var submitBackgroundColor = getStyleValue('style_submit_background_color');
    if (submitBackgroundColor) submitStyles['background-color'] = submitBackgroundColor;
    
    var submitPadding = getStyleValue('style_submit_padding');
    if (submitPadding) submitStyles['padding'] = parseStyleValue(submitPadding);
    
    var submitColor = getStyleValue('style_submit_color');
    if (submitColor) submitStyles['color'] = submitColor;
    
    var submitFontSize = getStyleValue('style_submit_font_size');
    if (submitFontSize) submitStyles['font-size'] = parseStyleValue(submitFontSize);

    var submitButton = doc.querySelector('.pfbc-buttons button, .pfbc-buttons input[type="submit"], button[type="submit"], input[type="submit"]');
    if (submitButton) {
      submitButton.style.cssText = buildStyleString(submitStyles);
    }
  }

  // Create debounced version for smoother UX
  var updatePreviewStylesDebounced = debounce(updatePreviewStyles, DEBOUNCE_DELAY);

  // ========================================================================
  // Combined Preview Update
  // ========================================================================
  
  /**
   * Update preview styles after iframe load.
   * Layout is NOT toggled here - the server-rendered iframe already has the
   * correct layout class and HTML structure. Layout changes go through
   * reloadPreviewWithLayout() which rebuilds the iframe entirely.
   */
  function updateFullPreview() {
    if (!previewReady) return;
    updatePreviewStyles();
  }

  // ========================================================================
  // Color Picker Initialization
  // ========================================================================
  
  /**
   * Initialize a color picker.
   *
   * Opening and closing is left entirely to wpColorPicker. Its open() already
   * closes every other picker (it fires click.wpcolorpicker on body), so no
   * custom "only one open at a time" handling is needed here - and any that
   * hides .iris-picker directly breaks the widget, because both open() and
   * close() call iris('toggle'), which flips on the element's current
   * visibility rather than on the widget's own state. Hiding the panel behind
   * the widget's back therefore inverts the next click.
   *
   * The hex input is kept visible at all times for keyboard access; admin.css
   * overrides the .hidden class WordPress puts on .wp-picker-input-wrap.
   */
  function initColorPicker(fieldKey) {
    var $input = $('#accua_form_' + fieldKey + ' .accua_form_value');
    if (!$input.length) return;

    $input.wpColorPicker({
      change: function(event, ui) {
        // wpColorPicker change event - update preview
        updatePreviewStylesDebounced();
      },
      clear: function() {
        updatePreviewStylesDebounced();
      }
    });

    // Iris registers a one-shot "show the panel on first focus" handler on the
    // input. It calls iris show() directly, so it opens the panel without
    // wpColorPicker marking the picker open. In stock WordPress that never
    // fires, because the hex input only exists while the picker is already
    // open; here the input is always visible, so a plain click into the field
    // would desync the widget. Drop it - the swatch button opens the panel,
    // and typing a hex value still works through Iris's change/keyup listeners.
    // Iris binds it without a namespace, so this can only be an unqualified
    // off() and must stay directly after the wpColorPicker() call above, while
    // that handler is still the only focus handler on the input.
    $input.off('focus');
  }

  // Initialize all color pickers
  $.each(COLOR_FIELDS, function(i, key) {
    initColorPicker(key);
  });

  // ========================================================================
  // Event Listeners - Style Fields
  // ========================================================================
  
  /**
   * Attach event listeners for a style field
   */
  function attachStyleFieldListeners(fieldKey, isColorField) {
    var $container = $('#accua_form_' + fieldKey);
    if (!$container.length) return;

    // Checkbox toggle - always triggers preview update
    $container.find('.accua_form_check_override').on('change', function() {
      updatePreviewStylesDebounced();
    });

    // Value input - for non-color fields
    if (!isColorField) {
      $container.find('.accua_form_value').on('input change', function() {
        updatePreviewStylesDebounced();
      });
    }
  }

  // Attach listeners to all form style fields
  $.each(ALL_STYLE_FIELDS.form, function(i, key) {
    attachStyleFieldListeners(key, COLOR_FIELDS.indexOf(key) !== -1);
  });

  // Attach listeners to all field style fields
  $.each(ALL_STYLE_FIELDS.field, function(i, key) {
    attachStyleFieldListeners(key, COLOR_FIELDS.indexOf(key) !== -1);
  });

  // Attach listeners to all submit style fields
  $.each(ALL_STYLE_FIELDS.submit, function(i, key) {
    attachStyleFieldListeners(key, COLOR_FIELDS.indexOf(key) !== -1);
  });

  // ========================================================================
  // Event Listeners - Layout
  // ========================================================================
  
  $('#accua_form_layout .accua_form_value').on('change', function() {
    // Reload preview with new layout - layout changes require HTML rebuild
    // (different layouts generate different HTML structure, not just CSS)
    reloadPreviewWithLayout($(this).val());
  });

  // ========================================================================
  // Preview Iframe Load Handler
  // ========================================================================
  
  function handlePreviewLoaded() {
    previewReady = true;

    // Remove loading state
    $('#accua_form_preview_area_wrapper').removeClass('accua-form-preview-loading');

    // Small delay to ensure iframe content is fully rendered
    setTimeout(function() {
      updateFullPreview();
    }, PREVIEW_LOAD_DELAY);
  }

  $('#accua_form_preview_area').on('load', handlePreviewLoaded);

  // If the server-rendered iframe finished loading before the handler above
  // was attached, the load event has already fired: without this the preview
  // never becomes ready and live style updates stay disabled until a reload.
  if (previewFrameAlreadyLoaded()) {
    handlePreviewLoaded();
  }

  // ========================================================================
  // Save Handler
  // ========================================================================
  
  $('.accua_form_save_settings_button').on('click', function(e) {
    e.preventDefault();

    var $clickedButton = $(this);
    
    // Store original button text and add saving state with "Saving..." text
    var originalText = $clickedButton.val();
    $saveButton.addClass('accua-saving').prop('disabled', true);
    $clickedButton.val(accua_forms_i18n.saving || 'Saving...').addClass('accua-saving-active');

    var formId = $('#accua_form_save_settings_id').val();

    // Build data object
    var data = {
      'action': 'accua-save-form-settings',
      'form-id': formId,
      '_nonce_edit_form': $('#_nonce_edit_form').val(),
      'title': $('#title').val(),
      'use_ajax': $('#accua_form_use_ajax .accua_form_value').is(':checked') ? 1 : 0
    };

    // Layout
    var layout = $('#accua_form_layout .accua_form_value').val();
    if (layout === 'toplabel' || layout === 'sidebyside' || layout === 'inlinelabel') {
      data.layout = layout;
    } else {
      data.layout = '';
    }

    // GADS conversion tracking
    var gads = $('#gads_conversion_code_input').val();
    if (gads && gads !== 'undefined' && gads !== 'null') {
      data.gads_conversion_tracking_code = gads;
    }

    // Collect all override fields
    var overrideFields = [
      'success_message', 'error_message',
      'emails_from_name', 'emails_from', 'admin_emails_to', 'emails_bcc',
      'admin_emails_subject', 'admin_emails_message',
      'confirmation_emails_subject', 'confirmation_emails_message'
    ];
    
    // Add all style fields
    $.each(ALL_STYLE_FIELDS.form, function(i, key) { overrideFields.push(key); });
    $.each(ALL_STYLE_FIELDS.field, function(i, key) { overrideFields.push(key); });
    $.each(ALL_STYLE_FIELDS.submit, function(i, key) { overrideFields.push(key); });

    $.each(overrideFields, function(i, key) {
      var $checkbox = $('#accua_form_' + key + ' .accua_form_check_override:checked');
      var checkValue = $checkbox.val();
      
      if (checkValue !== undefined && checkValue != 0) {
        if (checkValue == -1) {
          // "No message" option
          data[key] = '';
          data[key + '_no_message'] = 1;
        } else {
          var $element = $('#accua_form_' + key + ' .accua_form_value');
          if ($element.is('textarea')) {
            data[key] = getTinyMCEContent('accua_form_' + key);
          } else {
            data[key] = $element.val();
          }
        }
      }
    });

    // Data Retention fields
    data.submission_retention_override = $('#accua_form_retention_override').is(':checked') ? 1 : 0;
    data.submission_retention_value = $('#submission_retention_value').val();
    data.submission_retention_unit = $('#submission_retention_unit').val();
    data.submission_retention_mode = $('input[name="submission_retention_mode"]:checked').val();

    // The per-widget field saves, the order save and the settings save below
    // all read-modify-write the same draft transient server-side. They must
    // run strictly in sequence: fired concurrently they overwrite each
    // other's draft (lost update - e.g. a renamed title silently reverting).
    var runDraftSaveChain = function(done) {
      if (typeof accuaWidgets === 'undefined' || !accuaWidgets) {
        done();
        return;
      }
      var $widgets = $('#widgets-right div.widget');
      var saveWidgetAt = function(i) {
        if (i >= $widgets.length) {
          if (accuaWidgets.saveOrder) {
            accuaWidgets.saveOrder(null, function() { done(); });
          } else {
            done();
          }
          return;
        }
        accuaWidgets.save($widgets.eq(i), 0, 1, 0, function() { saveWidgetAt(i + 1); });
      };
      saveWidgetAt(0);
    };

    runDraftSaveChain(function() {

    // Step 1: Save settings to draft
    $.ajax({
      url: ajaxurl,
      type: 'POST',
      data: data,
      success: function() {
        // Step 2: Publish draft to live database
        $.ajax({
          url: ajaxurl,
          type: 'POST',
          data: {
            'action': 'accua-publish-form-draft',
            'form-id': formId,
            '_nonce_edit_form': $('#_nonce_edit_form').val()
          },
          success: function() {
            // Reload preview to show saved state (include current layout)
            reloadPreviewWithLayout($('#accua_form_layout .accua_form_value').val());

            // Update URL without reload
            try {
              if (history.pushState && window.location.search.indexOf('page=accua_forms_list') === -1) {
                history.pushState('', document.title, 'admin.php?page=accua_forms_list&fid=' + formId);
                window.onpopstate = function() { location.reload(); };
              }
            } catch (e) {}

            // Restore button text immediately (was cleared to show spinner)
            $clickedButton.val(originalText);
            
            // Show success state on button briefly
            $saveButton.removeClass('accua-saving').addClass('accua-saved').prop('disabled', false);
            $clickedButton.removeClass('accua-saving-active').addClass('accua-saved-active');
            
            // Remove success visual state after 1.5 seconds
            setTimeout(function() {
              $saveButton.removeClass('accua-saved');
              $clickedButton.removeClass('accua-saved-active');
            }, 1500);
          },
          error: function() {
            // Show error state
            $saveButton.removeClass('accua-saving').prop('disabled', false);
            $clickedButton.removeClass('accua-saving-active').val(originalText).addClass('accua-error');
            setTimeout(function() {
              $clickedButton.removeClass('accua-error');
            }, 3000);
          }
        });
      },
      error: function() {
        // Show error state
        $saveButton.removeClass('accua-saving').prop('disabled', false);
        $clickedButton.removeClass('accua-saving-active').val(originalText).addClass('accua-error');
        setTimeout(function() {
          $clickedButton.removeClass('accua-error');
        }, 3000);
      }
    });

    }); // end runDraftSaveChain
  });

  // ========================================================================
  // Messages Tab: Override Checkboxes Enable/Disable Their Text Input
  // ========================================================================

  /**
   * The email override inputs (To, Bcc, Subject, From name, From email) render
   * disabled until their "Customize" checkbox is checked. The checkbox name
   * matches its container id (e.g. accua_form_admin_emails_to). Select by
   * data-tab, not panel #id - tabs.js renames panel IDs at init. Appearance
   * tab rows need no JS: admin.css :has() rules show/hide those inputs.
   */
  $('.accua-tabs__panel[data-tab="messages"] input.accua_form_check_override[type="checkbox"]').on('change', function() {
    $('#' + this.name).find('.accua_form_value').first().prop('disabled', !this.checked);
  });

  // ========================================================================
  // Unsaved Changes Warning (WordPress standard beforeunload pattern)
  // ========================================================================

  var formDirty = false;

  function markDirty() {
    formDirty = true;
  }

  function markClean() {
    formDirty = false;
  }

  // Expose globally so form-fields.js (and other scripts) can mark dirty
  window.accuaFormsEditorDirty = {
    mark: markDirty,
    clean: markClean,
    isDirty: function() { return formDirty; }
  };

  // Track changes on all form inputs within the edit page
  $('#accua_forms_edit_page').on('input change', 'input, textarea, select', markDirty);

  // Track color picker changes (wpColorPicker fires irischange on the document)
  $(document).on('irischange', '#accua_forms_edit_page .wp-picker-container input', markDirty);

  // Note: Field drag/drop/reorder is tracked via window.accuaFormsEditorDirty.mark()
  // called directly from form-fields.js sortable stop and droppable drop handlers.

  // TinyMCE editors: mark dirty on content change
  function bindTinyMCEDirty(editor) {
    editor.on('input change keyup', markDirty);
  }
  $(document).on('tinymce-editor-init', function(event, editor) {
    bindTinyMCEDirty(editor);
  });
  // Also bind to any editors already initialized (race condition safety)
  if (typeof tinyMCE !== 'undefined' && tinyMCE.editors) {
    $.each(tinyMCE.editors, function(i, editor) {
      if (editor) { bindTinyMCEDirty(editor); }
    });
  }

  // Mark clean after successful save (publish draft)
  $(document).ajaxComplete(function(event, xhr, settings) {
    if (settings && settings.data && typeof settings.data === 'string' && settings.data.indexOf('action=accua-publish-form-draft') !== -1) {
      if (xhr.status === 200) {
        markClean();
      }
    }
  });

  // Allow intentional form submissions (e.g. delete form) without warning
  $('#accua_forms_edit_page').on('submit', 'form', markClean);

  // Browser beforeunload warning
  $(window).on('beforeunload', function() {
    if (formDirty) {
      return (typeof accua_forms_i18n !== 'undefined' && accua_forms_i18n.unsaved_changes)
        ? accua_forms_i18n.unsaved_changes
        : true;
    }
  });

});

```
