From 8ad4c5e6e1ab7b442d5c81157e8cb39b190c8ea1 Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Thu, 21 Mar 2019 16:51:52 +0000 Subject: [PATCH] Add separate overlap handling for textareas Our textareas are multi-line and can change in size based on their content. Because of this, we need to check the caret for overlapping, not the whole textarea. This adds separate tracking for this. --- .../stick-to-window-when-scrolling.js | 60 ++++++++++++++++--- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/stick-to-window-when-scrolling.js b/app/assets/javascripts/stick-to-window-when-scrolling.js index b804fe5e9..5825c47ea 100644 --- a/app/assets/javascripts/stick-to-window-when-scrolling.js +++ b/app/assets/javascripts/stick-to-window-when-scrolling.js @@ -29,17 +29,55 @@ }; ScrollArea.prototype.setEvents = function () { this.node.addEventListener('focus', this.focusHandler.bind(this), true); + $(this.node).on('keyup', 'textarea', this.focusHandler.bind(this)); }; ScrollArea.prototype.removeEvents = function () { this.node.removeEventListener('focus', this.focusHandler.bind(this)); + $(this.node).find('textarea').off('keyup', 'textarea', this.focusHandler.bind(this)); + }; + ScrollArea.prototype.getFocusedDetails = { + forElement: function ($focusedElement) { + focused = { + 'top': $focusedElement.offset().top, + 'height': $focusedElement.outerHeight(), + 'type': 'element' + }; + focused.bottom = focused.top + focused.height; + + return focused; + }, + forCaret: function (evt) { + var textarea = evt.target; + var caretCoordinates = window.getCaretCoordinates(textarea, textarea.selectionEnd); + var focused = { + 'top': $(textarea).offset().top + caretCoordinates.top, + 'height': caretCoordinates.height, + 'type': 'caret' + }; + + focused.bottom = focused.top + focused.height; + + return focused; + } }; ScrollArea.prototype.focusHandler = function (e) { var $focusedElement = $(document.activeElement); + var nodeName = $focusedElement.get(0).nodeName.toLowerCase(); var endOfFurthestEl = focusOverlap.endOfFurthestEl(this._els, this.edge); - var overlap = focusOverlap.getOverlap($focusedElement, this.edge, endOfFurthestEl); + var focused; + var overlap; + + // if textarea is focused, we care about checking the caret, not the whole element + if (nodeName === 'textarea') { + focused = this.getFocusedDetails.forCaret(e); + } else { + focused = this.getFocusedDetails.forElement($focusedElement); + } + + overlap = focusOverlap.getOverlap(focused, this.edge, endOfFurthestEl); if (overlap > 0) { - $(window).scrollTop($(window).scrollTop() + overlap); + focusOverlap.adjustForOverlap(focused, this.edge, overlap); } }; ScrollArea.prototype.destroy = function () { @@ -116,15 +154,13 @@ // Object collecting together methods for stopping sticky overlapping focused elements var focusOverlap = { - getOverlap: function ($focusedElement, edge, endOfFurthestEl) { - var topOfFocusedElement = $focusedElement.offset().top; - + getOverlap: function (focused, edge, endOfFurthestEl) { if (!endOfFurthestEl) { return 0; } if (edge === 'top') { - return endOfFurthestEl - topOfFocusedElement; + return endOfFurthestEl - focused.top; } else { - return (topOfFocusedElement + $focusedElement.outerHeight()) - endOfFurthestEl; + return focused.bottom - endOfFurthestEl; } }, endOfFurthestEl: function (els, edge) { @@ -149,6 +185,16 @@ return offsets.reduce(function (accumulator, offset) { return (accumulator < offset) ? offset: accumulator; }); + }, + adjustForOverlap: function (focused, edge, overlap) { + var scrollTop = $(window).scrollTop(); + + // scroll so element becomes visible + if (edge === 'top') { + $(window).scrollTop(scrollTop - overlap); + } else { + $(window).scrollTop(scrollTop + overlap); + } } };