diff --git a/lib/assets/javascripts/_modules/table-of-contents.js b/lib/assets/javascripts/_modules/table-of-contents.js index 32229f48..ea703493 100644 --- a/lib/assets/javascripts/_modules/table-of-contents.js +++ b/lib/assets/javascripts/_modules/table-of-contents.js @@ -104,7 +104,6 @@ function openNavigation () { $html.addClass('toc-open') - toggleBackgroundVisiblity(false) updateAriaAttributes() $toc.focus() } @@ -112,26 +111,30 @@ function closeNavigation () { $html.removeClass('toc-open') - toggleBackgroundVisiblity(true) - $openButton.focus() updateAriaAttributes() - } - - function toggleBackgroundVisiblity (visibility) { - $('.toc-open-disabled').attr('aria-hidden', visibility ? '' : 'true') + $openButton.focus() } function updateAriaAttributes () { var tocIsVisible = $toc.is(':visible') - var openButtonIsVisible = $openButton.is(':visible') + var tocIsDialog = $openButton.is(':visible') $($openButton).add($closeButton) .attr('aria-expanded', tocIsVisible ? 'true' : 'false') $toc.attr({ 'aria-hidden': tocIsVisible ? 'false' : 'true', - role: openButtonIsVisible ? 'dialog' : null + role: tocIsDialog ? 'dialog' : null }) + + $('.app-pane__content').attr('aria-hidden', (tocIsDialog && tocIsVisible) ? 'true' : 'false') + + // only make main content pane focusable if it scrolls independently of the toc + if (!tocIsDialog) { + $('.app-pane__content').attr('tabindex', '0') + } else { + $('.app-pane__content').removeAttr('tabindex') + } } function preventingScrolling (callback) { diff --git a/lib/assets/stylesheets/modules/_app-pane.scss b/lib/assets/stylesheets/modules/_app-pane.scss index 9e82194c..1874a2c8 100644 --- a/lib/assets/stylesheets/modules/_app-pane.scss +++ b/lib/assets/stylesheets/modules/_app-pane.scss @@ -61,3 +61,10 @@ } } +.app-pane__content:focus-visible, +.app-pane__content:has(main:focus-visible) { + outline: $govuk-focus-width solid transparent; + box-shadow: + 0 0 0 4px $govuk-focus-colour, + 0 0 0 8px $govuk-focus-text-colour; +} diff --git a/lib/source/layouts/core.erb b/lib/source/layouts/core.erb index d86aa1d3..499083dd 100644 --- a/lib/source/layouts/core.erb +++ b/lib/source/layouts/core.erb @@ -58,7 +58,7 @@ <% end %> -
+
<%= yield %> <%= partial "layouts/page_review" %> diff --git a/spec/javascripts/table-of-contents-spec.js b/spec/javascripts/table-of-contents-spec.js index c750c302..dc9412a3 100644 --- a/spec/javascripts/table-of-contents-spec.js +++ b/spec/javascripts/table-of-contents-spec.js @@ -5,6 +5,7 @@ describe('Table of contents', function () { var $html var $tocBase var $toc + var $mainContentPane var $closeButton var $openButton var $tocStickyHeader @@ -13,28 +14,33 @@ describe('Table of contents', function () { beforeAll(function () { $html = $('html') $tocBase = $( - '
' + - '' ) - .append($toc) + .append($tocClone) + $toc = $tocClone.eq(0).find('.toc') + $mainContentPane = $tocClone.eq(1) $closeButton = $toc.find('.js-toc-close') $openButton = $html.find('.js-toc-show') @@ -73,14 +81,15 @@ describe('Table of contents', function () { // clear up any classes left on $html.removeClass('toc-open') $html.find('body #toc-heading').remove() - $html.find('body .toc').remove() + $html.find('body .app-pane__toc').remove() + $html.find('body .app-pane__content').remove() if ($tocStickyHeader && $tocStickyHeader.length) { $tocStickyHeader.remove() } }) describe('when the module is started', function () { - it('on a mobile-size screen, it should mark the table of contents as hidden', function () { + it('on a mobile-size screen, it should hide the table of contents and stop the main content pane being focusable', function () { // styles applied by this test simulate the styles media-queries will apply on real web pages // the .mobile-size class hides the table of contents and the open button $html.addClass('mobile-size') // simulate the styles media-queries will apply on real web pages @@ -89,11 +98,12 @@ describe('Table of contents', function () { module.start($toc) expect($toc.attr('aria-hidden')).toEqual('true') + expect($mainContentPane.get(0).hasAttribute('tabindex')).toBe(false) $html.removeClass('mobile-size') }) - it('on a desktop-size screen, it should mark the table of contents as visible', function () { + it('on a desktop-size screen, it should show the table of contents and make the main content pane focusable', function () { // styles applied by this test simulate the styles media-queries will apply on real web pages // by default, they show the table of contents @@ -101,6 +111,7 @@ describe('Table of contents', function () { module.start($toc) expect($toc.attr('aria-hidden')).toEqual('false') + expect($mainContentPane.attr('tabindex')).toEqual('0') }) }) @@ -156,10 +167,15 @@ describe('Table of contents', function () { describe('if the open button is clicked', function () { beforeEach(function () { + $html.addClass('mobile-size') module = new GOVUK.Modules.TableOfContents() module.start($toc) }) + afterEach(function () { + $html.removeClass('toc-open mobile-size') + }) + it('the click event should be cancelled', function () { var clickEvt = new $.Event('click') @@ -168,7 +184,7 @@ describe('Table of contents', function () { expect(clickEvt.isDefaultPrevented()).toBe(true) }) - it('the table of contents should show and be focused', function () { + it('the table of contents should show and be focused and the main content hidden', function () { // detecting focus has proved unreliable so track calls to $toc.focus instead var _focus = $.fn.focus var tocFocusSpy = jasmine.createSpy('tocFocusSpy') @@ -188,6 +204,7 @@ describe('Table of contents', function () { $openButton.trigger(clickEvt) expect($toc.attr('aria-hidden')).toEqual('false') + expect($mainContentPane.attr('aria-hidden')).toEqual('true') expect(tocFocusSpy).toHaveBeenCalled() @@ -229,9 +246,13 @@ describe('Table of contents', function () { it('the button that triggered the dialog is refocused', function () { expect(document.activeElement).toBe($openButton.get(0)) }) + + it('the main content area should be shown', function () { + expect($mainContentPane.attr('aria-hidden')).toEqual('false') + }) }) - it('on mobile-size screens, when the table of contents is open and the escape key is activated, the table of contents should be hidden', function () { + it('on mobile-size screens, when the table of contents is open and the escape key is activated, the table of contents should be hidden and the main content shown', function () { $html.addClass('mobile-size') module = new GOVUK.Modules.TableOfContents() @@ -244,6 +265,8 @@ describe('Table of contents', function () { })) expect($html.hasClass('toc-open')).toBe(false) + expect($toc.attr('aria-hidden')).toEqual('true') + expect($mainContentPane.attr('aria-hidden')).toEqual('false') $html.removeClass('mobile-size') })