import { helper } from '@ember/component/helper'; import { next } from '@ember/runloop'; import { assert } from '@ember/debug'; function elementIsNotVisible(element: Element) { const elementPosition = element.getBoundingClientRect(); const windowHeight = window.innerHeight; // check if the element is within current view port if ( // above current view port elementPosition.top <= 0 || // below current view port elementPosition.bottom >= windowHeight ) { return true; } // check if element is within current view port button hidden behind // fixed bottom navigation bar const bottomNavEl = document.querySelector( '.cr-steps-bottom-nav', ) as HTMLElement | null; if (!bottomNavEl) { // bottom navigation bar can not overlay element if it does not exist return false; } return ( getComputedStyle(bottomNavEl).position === 'fixed' && elementPosition.bottom >= windowHeight - bottomNavEl.offsetHeight ); } const scrollFirstInvalidElementIntoViewPort = helper(() => { return () => { // `schedule('afterRender', function() {})` would be more approperiate but there seems to be a // timing issue in Firefox causing the Browser not scrolling up far enough if doing so // delaying to next runloop therefore next(function () { const invalidInput = document.querySelector( '.form-control.is-invalid, .custom-control-input.is-invalid', ) as HTMLInputElement; assert( 'Atleast one form control must be marked as invalid if form submission was rejected as invalid', invalidInput, ); // focus first invalid control invalidInput.focus({ preventScroll: true }); // scroll to label or legend of first invalid control if it's not visible yet if (elementIsNotVisible(invalidInput)) { // Radio groups have a label and a legend. While the label is per input, the legend is for // the whole group. Croodle should bring the legend into view in that case. // Due to a bug in Ember Bootstrap it renders a `