Scroll to required question

User
$('.gfield_choice:not(:last) label').on('click', function() {
$(this).siblings('input').trigger('click'); // update GF conditional logic first

var scrollToEl = '';
var $sourceBlockEl = $(this).closest('.gfield');
var $nextBlockEl = $sourceBlockEl.next();

var headerHeight = $('#et_mobile_nav_menu').is(':visible') ? 0 : $('#et-main-area').offset().top;

// Only treat as "correct" if not flagged incorrect AND gating allows
var isCorrectClick = ((($(this).attr('onclick') || '').indexOf('incorrect') === -1) && (isOverride || (typeof availableProducts !== 'undefined' && availableProducts.length > 0)));

if (isCorrectClick) {
// 1) Try going UP to nearest previous incorrect/error
var $incorrectFields = $('.gfield:visible').filter(function(){
var $f = $(this);
return $f.hasClass('gfield_error') ||
$f.hasClass('gquiz-field-incorrect') ||
$f.find('.gquiz-incorrect-choice:visible').length > 0 ||
$f.find('.choice_incorrect:visible').length > 0;
});

if ($incorrectFields.length) {
var srcTop = $sourceBlockEl.offset().top, $prevIncorrect = $();
$incorrectFields.each(function(){
var $f = $(this);
if ($f.offset().top < srcTop) $prevIncorrect = $f; // last one above = nearest previous
});
if ($prevIncorrect.length) {
scrollToEl = $prevIncorrect;

// mobile centering tweak for tall fields
if (scrollToEl.outerHeight() > $(window).outerHeight()) {
var $choice = scrollToEl.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').first();
if ($choice.length) scrollToEl = $choice;
headerHeight = $(window).outerHeight() / 4;
}
}
}

// 2) If no incorrect above, fall back to normal DOWN auto-scroll
if (!scrollToEl) {
// next visible unanswered block (fix typo: .length)
if ($nextBlockEl.is(':visible')
&& $nextBlockEl.find('input:checked').length === 0
&& $nextBlockEl.find('.choice_selected').length === 0) {
scrollToEl = $nextBlockEl;
} else {
// first visible unanswered anywhere (required OR optional)
var found = false;
$('.gfield:visible').each(function(){
var $f = $(this);
if ($f.find('.ginput_container').length === 0) return; // skip HTML/section

var answered = false;
if ($f.find('.ginput_container_choice').length) {
answered = $f.find('input:radio:checked, input:checkbox:checked').length > 0;
} else {
answered =
$f.find('input[type="text"], input[type="email"], input[type="number"], input[type="tel"], input[type="url"], textarea')
.filter(function(){ return $.trim($(this).val()) !== ''; }).length > 0
|| $f.find('select').filter(function(){ var v=$(this).val(); return v !== '' && v !== null; }).length > 0
|| $f.find('input[type="date"], input[type="time"]').filter(function(){ return $.trim($(this).val()) !== ''; }).length > 0;
}

if (!answered) { scrollToEl = $f; found = true; return false; }
});

if (!found) {
// nothing left unanswered → center submit
scrollToEl = $('.gform_footer');
headerHeight = headerHeight + ($(window).outerHeight() / 4);
}
}
}
}

// No scroll on incorrect clicks (per your requirement)
if (scrollToEl) {
var elOffset = $(scrollToEl).is(':visible')
? $(scrollToEl).offset().top
: $sourceBlockEl.offset().top + $sourceBlockEl.outerHeight();

$('html, body').animate({ scrollTop: elOffset – headerHeight }, 1000);
}
});
also allow to scroll up if correct choice seleced that required question user no given anser

Assistant
Got it — you want it to also scroll **up** to the nearest **required-but-unanswered** question when a correct choice is selected (in addition to your existing “scroll up to previous incorrect/error” behavior).

Drop-in replacement below. I added small helpers to detect **incorrect**, **required**, and **answered**. The “previous target” now picks the nearest field above that is either **incorrect** OR **required & unanswered**. Everything else (downward scroll, submit centering, header offset) stays the same.

$('.gfield_choice:not(:last) label').on('click', function () {
  $(this).siblings('input').trigger('click'); // update GF conditional logic first

  var $scrollTo = null;
  var $sourceBlockEl = $(this).closest('.gfield');
  var $nextBlockEl   = $sourceBlockEl.next();

  var headerHeight = $('#et_mobile_nav_menu').is(':visible') ? 0 : $('#et-main-area').offset().top;

  // Only treat as "correct" if not flagged incorrect AND gating allows
  var isCorrectClick = ((($(this).attr('onclick') || '').indexOf('incorrect') === -1) &&
                       (typeof isOverride !== 'undefined' && isOverride ||
                        (typeof availableProducts !== 'undefined' && availableProducts.length > 0)));

  // --- helpers ---
  function isIncorrect($f){
    return $f.hasClass('gfield_error') ||
           $f.hasClass('gquiz-field-incorrect') ||
           $f.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').length > 0;
  }

  function isRequired($f){
    return $f.hasClass('gfield_contains_required') ||
           $f.find('[aria-required="true"], [required]').length > 0;
  }

  function hasInputContainer($f){
    return $f.find('.ginput_container').length > 0;
  }

  function isAnswered($f){
    if (!hasInputContainer($f)) return true; // sections/HTML treated as “answered/skip”
    // choices
    if ($f.find('.ginput_container_choice, .ginput_container_radio, .ginput_container_checkbox').length) {
      return $f.find('input:radio:checked, input:checkbox:checked').length > 0;
    }
    // text-ish
    var textFilled = $f.find('input[type="text"], input[type="email"], input[type="number"], input[type="tel"], input[type="url"], textarea')
                       .filter(function(){ return $.trim($(this).val()) !== ''; }).length > 0;
    var selectFilled = $f.find('select').filter(function(){ var v=$(this).val(); return v !== '' && v !== null; }).length > 0;
    var dateTimeFilled = $f.find('input[type="date"], input[type="time"]')
                           .filter(function(){ return $.trim($(this).val()) !== ''; }).length > 0;
    return textFilled || selectFilled || dateTimeFilled;
  }
  // --- end helpers ---

  if (isCorrectClick) {
    // 1) Go UP to nearest previous field that is incorrect OR (required & unanswered)
    var $visibleFields = $('.gfield:visible');
    var idx = $visibleFields.index($sourceBlockEl);
    for (var i = idx - 1; i >= 0; i--) {
      var $f = $visibleFields.eq(i);
      if (isIncorrect($f) || (isRequired($f) && !isAnswered($f))) {
        $scrollTo = $f;
        break;
      }
    }

    // For tall targets on mobile, try to focus a meaningful sub-element
    if ($scrollTo && $scrollTo.outerHeight() > $(window).outerHeight()) {
      var $sub = $scrollTo.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').first();
      if (!$sub.length) {
        $sub = $scrollTo.find('input, select, textarea').filter(':visible').first();
      }
      if ($sub.length) $scrollTo = $sub;
      headerHeight = $(window).outerHeight() / 4;
    }

    // 2) If no target above, fall back to normal DOWN auto-scroll
    if (!$scrollTo) {
      if ($nextBlockEl.is(':visible') && !isAnswered($nextBlockEl)) {
        $scrollTo = $nextBlockEl;
      } else {
        // first visible unanswered anywhere (required OR optional)
        $('.gfield:visible').each(function(){
          var $f = $(this);
          if (!hasInputContainer($f)) return; // skip non-input sections
          if (!isAnswered($f)) { $scrollTo = $f; return false; }
        });
        if (!$scrollTo) {
          // nothing left unanswered → center submit
          $scrollTo = $('.gform_footer');
          headerHeight = headerHeight + ($(window).outerHeight() / 4);
        }
      }
    }
  }

  // No scroll on incorrect clicks
  if ($scrollTo && $scrollTo.length) {
    var elOffset = $scrollTo.is(':visible')
      ? $scrollTo.offset().top
      : $sourceBlockEl.offset().top + $sourceBlockEl.outerHeight();

    $('html, body').animate({ scrollTop: elOffset - headerHeight }, 1000);
  }
});

This keeps your current behavior, **adds** the “scroll up to nearest required-but-unanswered” when you pick a correct answer, and still never scrolls on incorrect clicks.

User
but only scroll up only if required quesion not non required
Assistant
Yep—just gate the **UP scan** by `isRequired(…)`. Replace your UP loop with this:

// 1) Go UP to nearest previous field that is REQUIRED and
//    (incorrect OR unanswered)
var $visibleFields = $('.gfield:visible');
var idx = $visibleFields.index($sourceBlockEl);
for (var i = idx - 1; i >= 0; i--) {
  var $f = $visibleFields.eq(i);
  if (isRequired($f) && (isIncorrect($f) || !isAnswered($f))) {
    $scrollTo = $f;
    break;
  }
}

Keep your helpers as-is (or like below):

function isIncorrect($f){
  return $f.hasClass('gfield_error') ||
         $f.hasClass('gquiz-field-incorrect') ||
         $f.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').length > 0;
}
function isRequired($f){
  return $f.hasClass('gfield_contains_required') ||
         $f.find('[aria-required="true"], [required]').length > 0;
}
function isAnswered($f){
  if (!$f.find('.ginput_container').length) return true;
  if ($f.find('.ginput_container_choice, .ginput_container_radio, .ginput_container_checkbox').length) {
    return $f.find('input:radio:checked, input:checkbox:checked').length > 0;
  }
  var textFilled   = $f.find('input[type="text"], input[type="email"], input[type="number"], input[type="tel"], input[type="url"], textarea').filter(function(){ return $.trim($(this).val()) !== ''; }).length > 0;
  var selectFilled = $f.find('select').filter(function(){ var v=$(this).val(); return v !== '' && v !== null; }).length > 0;
  var dtFilled     = $f.find('input[type="date"], input[type="time"]').filter(function(){ return $.trim($(this).val()) !== ''; }).length > 0;
  return textFilled || selectFilled || dtFilled;
}

Now it will only scroll up to **required** questions (skips non-required), even if they’re incorrect/unanswered.

User
give me full code
Assistant
Here you go — full drop-in code with the **UP scan restricted to required fields only**:

// Auto-scroll on correct choice
jQuery(function($){
  $('.gfield_choice:not(:last) label').on('click', function () {
    // Let GF update conditional logic first
    $(this).siblings('input').trigger('click');

    var $scrollTo = null;
    var $sourceBlockEl = $(this).closest('.gfield');
    var $nextBlockEl   = $sourceBlockEl.next();

    // Safe header offset (Divi)
    var headerHeight = 0;
    try {
      headerHeight = $('#et_mobile_nav_menu').is(':visible') ? 0 : ($('#et-main-area').offset() ? $('#et-main-area').offset().top : 0);
    } catch(e){ headerHeight = 0; }

    // Treat click as "correct" only if not flagged incorrect AND gating allows
    var isCorrectClick =
      ((($(this).attr('onclick') || '').indexOf('incorrect') === -1) &&
       ( (typeof window.isOverride !== 'undefined' && window.isOverride) ||
         (Array.isArray(window.availableProducts) && window.availableProducts.length > 0) ));

    // --- helpers ---
    function isIncorrect($f){
      return $f.hasClass('gfield_error') ||
             $f.hasClass('gquiz-field-incorrect') ||
             $f.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').length > 0;
    }

    function isRequired($f){
      return $f.hasClass('gfield_contains_required') ||
             $f.find('[aria-required="true"], [required]').length > 0;
    }

    function hasInputContainer($f){
      return $f.find('.ginput_container').length > 0;
    }

    function isAnswered($f){
      if (!hasInputContainer($f)) return true; // sections/HTML => skip as "answered"
      // choice fields
      if ($f.find('.ginput_container_choice, .ginput_container_radio, .ginput_container_checkbox').length) {
        return $f.find('input:radio:checked, input:checkbox:checked').length > 0;
      }
      // text-like
      var textFilled = $f.find('input[type="text"], input[type="email"], input[type="number"], input[type="tel"], input[type="url"], textarea')
                         .filter(function(){ return $.trim($(this).val()) !== ''; }).length > 0;
      var selectFilled = $f.find('select').filter(function(){ var v=$(this).val(); return v !== '' && v !== null; }).length > 0;
      var dateTimeFilled = $f.find('input[type="date"], input[type="time"]')
                             .filter(function(){ return $.trim($(this).val()) !== ''; }).length > 0;
      return textFilled || selectFilled || dateTimeFilled;
    }
    // --- end helpers ---

    if (isCorrectClick) {
      // 1) Go UP to nearest previous field that is REQUIRED and (incorrect OR unanswered)
      var $visibleFields = $('.gfield:visible');
      var idx = $visibleFields.index($sourceBlockEl);
      for (var i = idx - 1; i >= 0; i--) {
        var $f = $visibleFields.eq(i);
        if (isRequired($f) && (isIncorrect($f) || !isAnswered($f))) {
          $scrollTo = $f;
          break;
        }
      }

      // For tall targets on mobile, try a meaningful sub-element
      if ($scrollTo && $scrollTo.outerHeight() > $(window).outerHeight()) {
        var $sub = $scrollTo.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').first();
        if (!$sub.length) $sub = $scrollTo.find('input, select, textarea').filter(':visible').first();
        if ($sub.length) $scrollTo = $sub;
        headerHeight = $(window).outerHeight() / 4;
      }

      // 2) If no required target above, fall back to normal DOWN auto-scroll
      if (!$scrollTo) {
        if ($nextBlockEl.is(':visible') && !isAnswered($nextBlockEl)) {
          $scrollTo = $nextBlockEl;
        } else {
          // first visible unanswered anywhere (required OR optional)
          $('.gfield:visible').each(function(){
            var $f = $(this);
            if (!hasInputContainer($f)) return; // skip non-input sections
            if (!isAnswered($f)) { $scrollTo = $f; return false; }
          });
          if (!$scrollTo) {
            // nothing left unanswered → center submit
            $scrollTo = $('.gform_footer');
            headerHeight = headerHeight + ($(window).outerHeight() / 4);
          }
        }
      }
    }

    // No scroll on incorrect clicks
    if ($scrollTo && $scrollTo.length) {
      var elOffset = $scrollTo.is(':visible')
        ? $scrollTo.offset().top
        : $sourceBlockEl.offset().top + $sourceBlockEl.outerHeight();

      $('html, body').animate({ scrollTop: elOffset - headerHeight }, 1000);
    }
  });
});

**What changed vs your last snippet**
– UP loop now targets **only required** fields: `isRequired($f) && (isIncorrect($f) || !isAnswered($f))`.
– Safer checks for globals (`window.isOverride`, `window.availableProducts`).
– Guarded header offset.

User
now all scrolling not working
Assistant
Sounds like the handler isn’t firing (binding issue) or `isCorrectClick` is never true (too-strict gating). Here’s a tougher, delegated + delayed version that:

– binds via delegation (works after GF re-renders),
– waits a tick so GF conditional logic updates first,
– only scrolls **up** to **required** fields (incorrect or unanswered),
– keeps your down-scroll fallback.

Drop this in:

// Gravity Forms auto-scroll on correct choice
jQuery(function ($) {
  // delegated binding survives GF DOM re-renders
  $(document).on('click', '.gfield_choice label', function () {
    // let GF toggle inputs & conditional logic first
    var $label = $(this);
    var $input = $label.siblings('input');
    $input.trigger('click');

    // run after GF updates DOM (tiny delay is important)
    setTimeout(function () {
      var $scrollTo = null;
      var $sourceBlockEl = $label.closest('.gfield');

      // find the next visible input field (skip sections/HTML)
      function nextInputField($from) {
        var $n = $from.nextAll('.gfield:visible').filter(function () {
          return $(this).find('.ginput_container').length > 0;
        }).first();
        return $n.length ? $n : $();
      }
      var $nextBlockEl = nextInputField($sourceBlockEl);

      // header offset (Divi safe)
      var headerHeight = 0;
      try {
        var $main = $('#et-main-area');
        headerHeight = $('#et_mobile_nav_menu').is(':visible') ? 0 : ($main.length ? $main.offset().top : 0);
      } catch (e) { headerHeight = 0; }

      // ---- helpers ----
      function isIncorrect($f) {
        return $f.hasClass('gfield_error') ||
               $f.hasClass('gquiz-field-incorrect') ||
               $f.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').length > 0;
      }
      function isRequired($f) {
        return $f.hasClass('gfield_contains_required') ||
               $f.find('[aria-required="true"], [required]').length > 0;
      }
      function hasInputs($f) {
        return $f.find('.ginput_container').length > 0;
      }
      function isAnswered($f) {
        if (!hasInputs($f)) return true;
        // choice fields
        if ($f.find('.ginput_container_choice, .ginput_container_radio, .ginput_container_checkbox').length) {
          return $f.find('input:radio:checked, input:checkbox:checked').length > 0;
        }
        // text/select/date-like
        var textFilled = $f.find('input[type="text"], input[type="email"], input[type="number"], input[type="tel"], input[type="url"], textarea')
                           .filter(function () { return $.trim($(this).val()) !== ''; }).length > 0;
        var selectFilled = $f.find('select').filter(function () { var v = $(this).val(); return v !== '' && v !== null; }).length > 0;
        var dtFilled = $f.find('input[type="date"], input[type="time"]')
                         .filter(function () { return $.trim($(this).val()) !== ''; }).length > 0;
        return textFilled || selectFilled || dtFilled;
      }
      // consider the click "correct" only if label isn't marked incorrect
      // and gating passes (defaults to true if globals are absent)
      function isCorrectClick() {
        var attr = ($label.attr('onclick') || '');
        var notIncorrect = attr.indexOf('incorrect') === -1;
        var gating =
          (typeof window.isOverride !== 'undefined' && !!window.isOverride) ||
          (Array.isArray(window.availableProducts) ? window.availableProducts.length > 0 : true);
        return notIncorrect && gating;
      }
      // ---- /helpers ----

      if (isCorrectClick()) {
        // 1) UP: nearest REQUIRED field above that is incorrect OR unanswered
        var $visibleFields = $('.gfield:visible');
        var idx = $visibleFields.index($sourceBlockEl);
        for (var i = idx - 1; i >= 0; i--) {
          var $f = $visibleFields.eq(i);
          if (isRequired($f) && (isIncorrect($f) || !isAnswered($f))) {
            $scrollTo = $f;
            break;
          }
        }

        // Tall target? aim at a sub-element for better centering
        if ($scrollTo && $scrollTo.outerHeight() > $(window).outerHeight()) {
          var $sub = $scrollTo.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').first();
          if (!$sub.length) $sub = $scrollTo.find('input, select, textarea').filter(':visible').first();
          if ($sub.length) $scrollTo = $sub;
          headerHeight = $(window).outerHeight() / 4;
        }

        // 2) No required target above → normal DOWN auto-scroll
        if (!$scrollTo || !$scrollTo.length) {
          if ($nextBlockEl.length && !isAnswered($nextBlockEl)) {
            $scrollTo = $nextBlockEl;
          } else {
            // first visible unanswered anywhere (required OR optional)
            $('.gfield:visible').each(function () {
              var $f = $(this);
              if (!hasInputs($f)) return; // skip non-input sections
              if (!isAnswered($f)) { $scrollTo = $f; return false; }
            });
            if (!$scrollTo || !$scrollTo.length) {
              // nothing left unanswered → center submit/footer
              $scrollTo = $('.gform_footer').first();
              if ($scrollTo.length) headerHeight = headerHeight + ($(window).outerHeight() / 4);
            }
          }
        }
      }

      // Only scroll on "correct" clicks
      if ($scrollTo && $scrollTo.length) {
        var elOffset = $scrollTo.is(':visible')
          ? $scrollTo.offset().top
          : $sourceBlockEl.offset().top + $sourceBlockEl.outerHeight();

        $('html, body').animate({ scrollTop: elOffset - headerHeight }, 600);
      }
    }, 40); // tiny delay so GF logic finishes
  });
});

**Why this should fix it**
– Uses delegated binding (`$(document).on('click', …)`) so the handler still works after GF redraws fields.
– Waits 40ms before computing targets so conditional logic has updated the DOM.
– `isCorrectClick()` now defaults gating to true if your globals aren’t present, so it won’t silently block scrolling.
– UP scan restricted to **required** only, exactly as you wanted.

If it’s still not moving, quickly check in DevTools:

// while clicking a label, see what this prints
window.gfdbg = true;

…and temporarily add `console.log({correct:isCorrectClick()})` inside the handler to verify the gating. If that logs `false`, your `onclick` doesn’t contain “incorrect” or the gating vars are off—easy to tweak.

User
I mean scrolll not wokring at all neither down nor up
Assistant
Gotcha. Likely the click fires but (a) the page’s **real scroll container** isn’t `html, body` (Divi often uses `#page-container`), and/or (b) the **“correct click”** check is too strict. Paste this full, hardened version — it:

– waits for GF conditional logic to finish,
– detects “correct” via quiz classes (more reliable than `onclick`),
– scrolls **up only to REQUIRED** fields (incorrect or unanswered),
– finds the *actual* scrollable container and scrolls it.

// Gravity Forms: auto-scroll on correct choice
// - UP only to REQUIRED (incorrect or unanswered)
// - DOWN fallback to next unanswered
// - Robust scroller detection (Divi/Elementor/etc.)
jQuery(function ($) {
  // delegated binding = survives GF DOM changes
  $(document).on('click', '.gfield_choice label', function () {
    var $label = $(this);

    // Let native label→input click + GF conditional logic run first
    setTimeout(function run() {
      var $scrollTo = $();
      var $source = $label.closest('.gfield');

      // ---------- helpers ----------
      function isIncorrect($f) {
        // field flagged incorrect OR has visible incorrect choice
        return $f.hasClass('gfield_error') ||
               $f.hasClass('gquiz-field-incorrect') ||
               $f.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').length > 0;
      }
      function isRequired($f) {
        return $f.hasClass('gfield_contains_required') ||
               $f.find('[aria-required="true"], [required]').length > 0;
      }
      function hasInputs($f) {
        return $f.find('.ginput_container').length > 0;
      }
      function isAnswered($f) {
        if (!hasInputs($f)) return true; // sections/HTML
        // choice fields
        if ($f.find('.ginput_container_choice, .ginput_container_radio, .ginput_container_checkbox').length) {
          return $f.find('input:radio:checked, input:checkbox:checked').length > 0;
        }
        // text-like
        var textFilled = $f.find('input[type="text"], input[type="email"], input[type="number"], input[type="tel"], input[type="url"], textarea')
                           .filter(function () { return $.trim($(this).val()) !== ''; }).length > 0;
        var selectFilled = $f.find('select').filter(function () { var v = $(this).val(); return v !== '' && v !== null; }).length > 0;
        var dtFilled = $f.find('input[type="date"], input[type="time"]')
                         .filter(function () { return $.trim($(this).val()) !== ''; }).length > 0;
        return textFilled || selectFilled || dtFilled;
      }
      function nextInputField($from) {
        var $n = $from.nextAll('.gfield:visible').filter(function () {
          return hasInputs($(this));
        }).first();
        return $n.length ? $n : $();
      }
      function getHeaderOffset() {
        try {
          if ($('#et_mobile_nav_menu').is(':visible')) return 0;
          var $main = $('#et-main-area');
          return $main.length ? ($main.offset().top || 0) : 0;
        } catch (e) { return 0; }
      }
      function getScroller() {
        // try likely containers; pick the first that actually scrolls
        var candidates = [
          'html, body',
          '#page-container',
          '.et_builder_inner_content',
          '.et_pb_section',
          'main',
          '.site-main'
        ];
        for (var i = 0; i < candidates.length; i++) {
          var $el = $(candidates[i]);
          if (!$el.length) continue;
          var t = $el.scrollTop();
          $el.scrollTop(t + 1);
          var moved = $el.scrollTop() !== t;
          $el.scrollTop(t);
          if (moved) return $el;
        }
        return $('html, body');
      }
      function smoothScrollTo($target, offset) {
        var top = ($target.offset() ? $target.offset().top : 0) - offset;
        if ('scrollBehavior' in document.documentElement.style) {
          // native smooth scroll
          window.scrollTo({ top: top, behavior: 'smooth' });
        } else {
          // jQuery fallback on the real scroller
          getScroller().animate({ scrollTop: top }, 600);
        }
      }
      function isCorrectClick() {
        // Prefer quiz classes over brittle onclick text
        var isMarkedIncorrect = $label.is('.gquiz-incorrect-choice, .choice_incorrect') ||
                                $label.closest('li, .gchoice').is('.gquiz-incorrect-choice, .choice_incorrect');
        // gating: if globals exist, use them; otherwise don't block
        var gating =
          (typeof window.isOverride !== 'undefined' ? !!window.isOverride : true) &&
          (typeof window.availableProducts !== 'undefined'
            ? (Array.isArray(window.availableProducts) ? window.availableProducts.length > 0 : !!window.availableProducts)
            : true);
        return !isMarkedIncorrect && gating;
      }
      // ---------- /helpers ----------

      if (!isCorrectClick()) {
        return; // no scroll on incorrect/blocked click
      }

      var headerOffset = getHeaderOffset();

      // 1) UP: nearest REQUIRED field above that is incorrect OR unanswered
      var $visible = $('.gfield:visible');
      var idx = $visible.index($source);
      for (var i = idx - 1; i >= 0; i--) {
        var $f = $visible.eq(i);
        if (isRequired($f) && (isIncorrect($f) || !isAnswered($f))) {
          $scrollTo = $f;
          break;
        }
      }

      // For tall fields, aim at a visible control
      if ($scrollTo.length && $scrollTo.outerHeight() > $(window).outerHeight()) {
        var $sub = $scrollTo.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').first();
        if (!$sub.length) $sub = $scrollTo.find('input, select, textarea').filter(':visible').first();
        if ($sub.length) $scrollTo = $sub;
        headerOffset = $(window).outerHeight() / 4;
      }

      // 2) No required target above → DOWN to next unanswered
      if (!$scrollTo.length) {
        var $next = nextInputField($source);
        if ($next.length && !isAnswered($next)) {
          $scrollTo = $next;
        } else {
          // first visible unanswered anywhere (required OR optional)
          $('.gfield:visible').each(function () {
            var $f = $(this);
            if (!hasInputs($f)) return; // skip non-input sections
            if (!isAnswered($f)) { $scrollTo = $f; return false; }
          });
          if (!$scrollTo.length) {
            // nothing left → center submit/footer
            $scrollTo = $('.gform_footer:visible').first();
            if ($scrollTo.length) headerOffset = headerOffset + ($(window).outerHeight() / 4);
          }
        }
      }

      if ($scrollTo.length) {
        smoothScrollTo($scrollTo, headerOffset);
      } else {
        // last resort: ensure at least some motion so you can tell it runs
        // comment this out later if not needed
        // getScroller().animate({ scrollTop: ($(window).scrollTop() + 1) }, 1);
      }
    }, 120); // a bit more time for GF conditional logic to finish
  });
});

**If it still doesn’t move**, quick checks:
1. In DevTools console, run `window.gfdbg=true` and temporarily add:

console.log('correct?', isCorrectClick());

inside the handler to ensure it’s `true` on your expected clicks.
2. Verify what scrolls on your page: in console run:

(function(){var els=['html, body','#page-container','.et_builder_inner_content','main']; els.forEach(s=>{var $e=jQuery(s); if($e.length){var t=$e.scrollTop(); $e.scrollTop(t+1); console.log(s,'moves?', $e.scrollTop()!==t); $e.scrollTop(t);}})();

If only `#page-container` moves, the helper will pick it automatically.

Leave a Comment

Your email address will not be published. Required fields are marked *