diff --git a/js/formidable.js b/js/formidable.js index 465389615e..94304d0a6b 100644 --- a/js/formidable.js +++ b/js/formidable.js @@ -1507,9 +1507,11 @@ function frmFrontFormJS() { summary.focus(); return; } + // No summary in the DOM (js_validate's client-side path never renders one): fall + // through to the first-errored-field focus below. } - if ( ! config.focusFirstError ) { + if ( ! config.focusFirstError && ! config.focusErrorSummary ) { return; } diff --git a/js/formidable.min.js b/js/formidable.min.js index 963b905235..78e659e531 100644 --- a/js/formidable.min.js +++ b/js/formidable.min.js @@ -45,7 +45,7 @@ error.remove())}function scrollToFirstField(object){if("function"===typeof objec function addLoadingClass($object){const loadingClass=isGoingToPrevPage($object)?"frm_loading_prev":"frm_loading_form";$object.addClass(loadingClass)}function isGoingToPrevPage($object){return typeof frmProForm!=="undefined"&&frmProForm.goingToPreviousPage($object)}function removeSubmitLoading(_,enable,processesRunning){if(processesRunning>0)return;document.querySelectorAll(".frm_loading_form").forEach(function(form){form.classList.remove("frm_loading_form","frm_loading_prev");jQuery(form).trigger("frmEndFormLoading"); if(enable==="enable"){enableSubmitButton(form);enableSaveDraft(form)}})}function showFileLoading(object){const loading=document.getElementById("frm_loading");if(!loading)return;const fileInput=object.querySelector("input[type=file]");const fileval=fileInput?fileInput.value:"";if(fileval!=="")setTimeout(function(){jQuery(loading).fadeIn("slow")},2E3)}function confirmClick(){const message=this.dataset.frmconfirm;return confirm(message)}function onHoneypotFieldChange(){const css=window.getComputedStyle(this).boxShadow; if(css?.match(/inset/))this.remove()}function changeFocusWhenClickComboFieldLabel(){let label;const comboInputsContainer=document.querySelectorAll(".frm_combo_inputs_container");comboInputsContainer.forEach(function(inputsContainer){if(!inputsContainer.closest(".frm_form_field"))return;label=inputsContainer.closest(".frm_form_field").querySelector(".frm_primary_label");if(!label)return;label.addEventListener("click",function(){inputsContainer.querySelector(".frm_form_field:first-child input, .frm_form_field:first-child select, .frm_form_field:first-child textarea").focus()})})} -function maybeFocusOnComboSubField(element){if("FIELDSET"!==element.nodeName)return false;if(!element.querySelector(".frm_combo_inputs_container"))return false;const comboSubfield=element.querySelector('[aria-invalid="true"]');if(comboSubfield){focusInput(comboSubfield);return true}return false}function checkForErrorsAndMaybeSetFocus(){const errors=document.querySelectorAll(".frm_form_field .frm_error, .frm_form_field [data-frm-error]");if(!errors.length)return;const formContainer=errors[0].closest(".frm-show-form");const config=getErrorConfigForForm(formContainer);if(config.focusErrorSummary){const summary=formContainer?formContainer.querySelector("[data-frm-error-summary]"):null;if(summary){summary.focus();return}}if(!config.focusFirstError)return;let element=errors[0];let timeoutCallback; +function maybeFocusOnComboSubField(element){if("FIELDSET"!==element.nodeName)return false;if(!element.querySelector(".frm_combo_inputs_container"))return false;const comboSubfield=element.querySelector('[aria-invalid="true"]');if(comboSubfield){focusInput(comboSubfield);return true}return false}function checkForErrorsAndMaybeSetFocus(){const errors=document.querySelectorAll(".frm_form_field .frm_error, .frm_form_field [data-frm-error]");if(!errors.length)return;const formContainer=errors[0].closest(".frm-show-form");const config=getErrorConfigForForm(formContainer);if(config.focusErrorSummary){const summary=formContainer?formContainer.querySelector("[data-frm-error-summary]"):null;if(summary){summary.focus();return}}if(!config.focusFirstError&&!config.focusErrorSummary)return;let element=errors[0];let timeoutCallback; do{element=element.previousSibling;if(!element)break;if(["input","select","textarea"].includes(element.nodeName.toLowerCase())){focusInput(element);break}if(maybeFocusOnComboSubField(element))break;if(element.classList!==undefined){if(element.classList.contains("html-active"))timeoutCallback=function(){const textarea=element.querySelector("textarea");if(null!==textarea)textarea.focus()};else if(element.classList.contains("tmce-active"))timeoutCallback=function(){tinyMCE.activeEditor.focus()};else if(element.classList.contains("frm_opt_container")){const firstInput= element.querySelector("input");if(firstInput){focusInput(firstInput);break}}if("function"===typeof timeoutCallback){setTimeout(timeoutCallback,0);break}}}while(element.previousSibling)}function focusInput(input){if(input.offsetParent!==null)input.focus();else triggerCustomEvent(document,"frmMaybeDelayFocus",{input})}function focusFieldFromErrorLink(event){const href=this.getAttribute("href");if(!href||!href.startsWith("#"))return;const container=document.getElementById(href.substring(1));if(!container)return; event.preventDefault();container.scrollIntoView({behavior:"smooth",block:"center"});const input=getFocusableInputInField(container);if(input){focusInput(input);return}container.setAttribute("tabindex","-1");focusInput(container)}function getFocusableInputInField(container){const inputs=Array.from(container.querySelectorAll(FOCUSABLE_FIELD_SELECTOR)).filter(inputCanTakeFocus);if(!inputs.length)return null;const invalidInput=inputs.find(input=>"true"===input.getAttribute("aria-invalid"));if(invalidInput)return invalidInput; diff --git a/tests/cypress/e2e/Forms/fieldsInFormBuilder-validation.cy.js b/tests/cypress/e2e/Forms/fieldsInFormBuilder-validation.cy.js index b3b3371654..e77c399cfd 100644 --- a/tests/cypress/e2e/Forms/fieldsInFormBuilder-validation.cy.js +++ b/tests/cypress/e2e/Forms/fieldsInFormBuilder-validation.cy.js @@ -180,6 +180,9 @@ describe( 'Fields in the form builder', () => { cy.get( `[id^="frm_error_field_"]` ).eq( 0 ).should( 'contain', `Text cannot be blank.` ); cy.get( `[id^="frm_error_field_"]` ).eq( 1 ).should( 'not.exist' ); cy.get( `[id^="frm_error_field_"]` ).eq( 2 ).should( 'not.exist' ); + // js_validate's client-side path never renders the error summary markup, so focus + // must fall back to the first errored field. + cy.get( '[id^="field_"]' ).filter( 'input, textarea' ).eq( 0 ).should( 'have.focus' ); cy.get( '[id^="field_"]' ).filter( 'input, textarea' ).eq( 1 ).clear(); cy.get( '[id^="field_"]' ).filter( 'input, textarea' ).eq( 2 ).clear(); cy.get( "button[type='submit']" ).should( 'contain', 'Submit' ).click();