diff --git a/app/assets/javascripts/stick-to-window-when-scrolling.js b/app/assets/javascripts/stick-to-window-when-scrolling.js
index c75779109..da1129742 100644
--- a/app/assets/javascripts/stick-to-window-when-scrolling.js
+++ b/app/assets/javascripts/stick-to-window-when-scrolling.js
@@ -10,7 +10,10 @@
var $el = el.$fixedEl;
var $scrollArea = $el.closest('.sticky-scroll-area');
- $scrollArea = $scrollArea.length ? $scrollArea : $el.parent();
+ if($scrollArea.length === 0) {
+ $scrollArea = $el.parent();
+ $scrollArea.addClass('sticky-scroll-area');
+ }
this._els = [el];
this.edge = edge;
@@ -46,11 +49,11 @@
return focused;
},
- forCaret: function (evt) {
- var textarea = evt.target;
+ forCaret: function ($textarea) {
+ var textarea = $textarea.get(0);
var caretCoordinates = window.getCaretCoordinates(textarea, textarea.selectionEnd);
var focused = {
- 'top': $(textarea).offset().top + caretCoordinates.top,
+ 'top': $textarea.offset().top + caretCoordinates.top,
'height': caretCoordinates.height,
'type': 'caret'
};
@@ -61,21 +64,23 @@
}
};
ScrollArea.prototype.focusHandler = function (e) {
- var $focusedElement = $(document.activeElement);
- var nodeName = $focusedElement.get(0).nodeName.toLowerCase();
+ this.scrollToRevealElement($(document.activeElement));
+ };
+ ScrollArea.prototype.scrollToRevealElement = function ($el) {
+ var nodeName = $el.get(0).nodeName.toLowerCase();
var endOfFurthestEl = focusOverlap.endOfFurthestEl(this._els, this.edge);
var isInSticky = function () {
- return $focusedElement.closest(this.selector).length > 0;
+ return $el.closest(this.selector).length > 0;
}.bind(this);
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);
+ focused = this.getFocusedDetails.forCaret($el);
} else {
if (isInSticky()) { return; }
- focused = this.getFocusedDetails.forElement($focusedElement);
+ focused = this.getFocusedDetails.forElement($el);
}
overlap = focusOverlap.getOverlap(focused, this.edge, endOfFurthestEl);
@@ -586,6 +591,18 @@
this.syncWithDOM(onSyncComplete);
};
+ // Public method to scroll so an element isn't covered by the sticky nav
+ Sticky.prototype.scrollToRevealElement = function (el) {
+ var $el = $(el);
+ var scrollAreaNode = $el.closest('.sticky-scroll-area').get(0);
+ var matches = $.grep(scrollAreas._scrollAreas, function (scrollArea) {
+ return scrollArea.node === scrollAreaNode;
+ });
+
+ if (matches.length) {
+ matches[0].scrollToRevealElement($el);
+ }
+ };
Sticky.prototype.setElWidth = function (el) {
var $el = el.$fixedEl;
var scrollArea = scrollAreas.getAreaByEl(el);
diff --git a/tests/javascripts/stick-to-window-when-scrolling.test.js b/tests/javascripts/stick-to-window-when-scrolling.test.js
index 5b6314a58..f94a220f9 100644
--- a/tests/javascripts/stick-to-window-when-scrolling.test.js
+++ b/tests/javascripts/stick-to-window-when-scrolling.test.js
@@ -292,6 +292,61 @@ describe("Stick to top/bottom of window when scrolling", () => {
});
+ describe("if scrollToRevealElement is called with an element", () => {
+
+ let link;
+ let linkBottom;
+
+ beforeEach(() => {
+
+ const inputFormBottom = getScreenItemBottomPosition(inputForm);
+
+ inputForm.insertAdjacentHTML('afterEnd', 'Formatting options');
+ link = document.querySelector('#formatting-options');
+
+ screenMock.mockPositionAndDimension('link', link, {
+ offsetHeight: 25, // 143px smaller than the sticky
+ offsetWidth: 727,
+ offsetTop: inputFormBottom
+ });
+
+ linkBottom = getScreenItemBottomPosition(link);
+
+ // move the sticky over the link. It's 168px high so this position will cause it to overlap.
+ screenMock.scrollTo(link.offsetTop - 140);
+
+ window.GOVUK.stickAtTopWhenScrolling.init();
+
+ });
+
+ afterEach(() => {
+
+ screenMock.window.spies.window.scrollTo.mockClear();
+
+ });
+
+ test("the window should scroll so the element is revealed", () => {
+
+ // update inputForm position as DOM normally would
+ inputForm.offsetTop = screenMock.window.top;
+
+ let stickyPosition = getStickyGroupPosition(screenMock, { stickyEls: [inputForm], edge: 'top' });
+
+ // sticky position should overlap link position
+ expect(stickyPosition.top).toBeLessThanOrEqual(link.offsetTop);
+ expect(stickyPosition.bottom).toBeGreaterThanOrEqual(linkBottom);
+
+ window.GOVUK.stickAtTopWhenScrolling.scrollToRevealElement(link);
+
+ stickyPosition = getStickyGroupPosition(screenMock, { stickyEls: [inputForm], edge: 'top' });
+
+ // the bottom of the sticky element should be at the top of the link
+ expect(screenMock.window.spies.window.scrollTo.mock.calls[0]).toEqual([0, link.offsetTop - stickyPosition.height]);
+
+ });
+
+ });
+
describe("if element is made sticky and another element underneath it is focused", () => {
let checkbox;
@@ -904,6 +959,61 @@ describe("Stick to top/bottom of window when scrolling", () => {
});
+ describe("if scrollToRevealElement is called with an element", () => {
+
+ let link;
+ let linkBottom;
+
+ beforeEach(() => {
+
+ const contentBottom = getScreenItemBottomPosition(content);
+
+ content.insertAdjacentHTML('afterEnd', 'Formatting options');
+ link = document.querySelector('#formatting-options');
+
+ screenMock.mockPositionAndDimension('link', link, {
+ offsetHeight: 25, // 25px smaller than the sticky
+ offsetWidth: 727,
+ offsetTop: contentBottom
+ });
+
+ linkBottom = getScreenItemBottomPosition(link);
+
+ // move the sticky over the link. It's 50px high so this position will cause it to overlap.
+ screenMock.scrollTo((linkBottom - windowHeight) + 5);
+
+ window.GOVUK.stickAtBottomWhenScrolling.init();
+
+ });
+
+ afterEach(() => {
+
+ screenMock.window.spies.window.scrollTo.mockClear();
+
+ });
+
+ test("the window should scroll so the element is revealed", () => {
+
+ // update inputForm position as DOM normally would
+ pageFooter.offsetTop = screenMock.window.bottom - pageFooter.offsetHeight;
+
+ let stickyPosition = getStickyGroupPosition(screenMock, { stickyEls: [pageFooter], edge: 'bottom' });
+
+ // sticky position should overlap link position
+ expect(stickyPosition.top).toBeLessThanOrEqual(link.offsetTop);
+ expect(stickyPosition.bottom).toBeGreaterThanOrEqual(linkBottom);
+
+ window.GOVUK.stickAtBottomWhenScrolling.scrollToRevealElement(link)
+
+ stickyPosition = getStickyGroupPosition(screenMock, { stickyEls: [pageFooter], edge: 'bottom' });
+
+ // the top of the sticky element should be at the bottom of the link
+ expect(screenMock.window.spies.window.scrollTo.mock.calls[0]).toEqual([0, (linkBottom + pageFooter.offsetHeight) - windowHeight]);
+
+ });
+
+ });
+
describe("if viewport bottom starts above element bottom", () => {
let pageFooterBottom;