From b8c5ab5e388a40b2fd7da2b5950cf57490a3f4aa Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Mon, 11 Mar 2019 16:26:11 +0000 Subject: [PATCH] Handle overlaps in scroll areas on focus events When focus changes in scroll areas, check the current focused element isn't overlapped by sticky elements in the area. If there are overlaps, mimic what browsers do if focus moves outside the viewport and scroll to move the focused element into view. --- .../stick-to-window-when-scrolling.js | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/app/assets/javascripts/stick-to-window-when-scrolling.js b/app/assets/javascripts/stick-to-window-when-scrolling.js index f00a3bc2a..b804fe5e9 100644 --- a/app/assets/javascripts/stick-to-window-when-scrolling.js +++ b/app/assets/javascripts/stick-to-window-when-scrolling.js @@ -16,6 +16,7 @@ this._els = [el]; this.edge = edge; this.node = scrollArea; + this.setEvents(); }; ScrollArea.prototype.addEl = function (el) { this._els.push(el); @@ -26,6 +27,24 @@ ScrollArea.prototype.updateEls = function (usedEls) { this._els = usedEls; }; + ScrollArea.prototype.setEvents = function () { + this.node.addEventListener('focus', this.focusHandler.bind(this), true); + }; + ScrollArea.prototype.removeEvents = function () { + this.node.removeEventListener('focus', this.focusHandler.bind(this)); + }; + ScrollArea.prototype.focusHandler = function (e) { + var $focusedElement = $(document.activeElement); + var endOfFurthestEl = focusOverlap.endOfFurthestEl(this._els, this.edge); + var overlap = focusOverlap.getOverlap($focusedElement, this.edge, endOfFurthestEl); + + if (overlap > 0) { + $(window).scrollTop($(window).scrollTop() + overlap); + } + }; + ScrollArea.prototype.destroy = function () { + this.removeEvents(); + }; // Object collecting together methods for interacting with scrollareas var scrollAreas = { @@ -95,6 +114,44 @@ } }; + // Object collecting together methods for stopping sticky overlapping focused elements + var focusOverlap = { + getOverlap: function ($focusedElement, edge, endOfFurthestEl) { + var topOfFocusedElement = $focusedElement.offset().top; + + if (!endOfFurthestEl) { return 0; } + + if (edge === 'top') { + return endOfFurthestEl - topOfFocusedElement; + } else { + return (topOfFocusedElement + $focusedElement.outerHeight()) - endOfFurthestEl; + } + }, + endOfFurthestEl: function (els, edge) { + var stuckEls = $.grep(els, function (el) { return el.isStuck(); }); + var edgeOfEl; + var offsets; + + if (edge === 'bottom') { + edgeOfEl = function (el) { + return el.$fixedEl.offset().top; + }; + } else { + edgeOfEl = function (el) { + return el.$fixedEl.offset().top + el.height; + }; + } + + if (!stuckEls.length) { return false; } + + offsets = $.map(stuckEls, function (el) { return edgeOfEl(el); }); + + return offsets.reduce(function (accumulator, offset) { + return (accumulator < offset) ? offset: accumulator; + }); + } + }; + // Object collecting together methods for dealing with marking the edge of a sticky, or group of // sticky elements (as seen in dialog mode) var oppositeEdge = {