From 720ac53e7e5352990ba5f9ba7de932a4e4fa5568 Mon Sep 17 00:00:00 2001 From: James Jacobs <jamesjacobs1@mac.com> Date: Mon, 7 Sep 2020 08:26:10 +0100 Subject: [PATCH 01/15] Fix broken lexicon page --- views/lexicon/tabs/buttons_labels.html.twig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/views/lexicon/tabs/buttons_labels.html.twig b/views/lexicon/tabs/buttons_labels.html.twig index 8c2012b977..4c30ff4b11 100644 --- a/views/lexicon/tabs/buttons_labels.html.twig +++ b/views/lexicon/tabs/buttons_labels.html.twig @@ -699,7 +699,8 @@ }) }} - </p> + </div> + </p> <h2 class="heading">All the things!</h2> <p> From 961c75938d72f1665b38021558d67424d008e8e8 Mon Sep 17 00:00:00 2001 From: James Jacobs <jamesjacobs1@mac.com> Date: Mon, 7 Sep 2020 08:27:11 +0100 Subject: [PATCH 02/15] Fix nav focus issue --- js/NavMainComponent.js | 94 ++++++++++++++++++++++------ tests/js/web/NavMainComponentTest.js | 42 +++++++++++++ 2 files changed, 116 insertions(+), 20 deletions(-) diff --git a/js/NavMainComponent.js b/js/NavMainComponent.js index f389c776dc..32edee1fbe 100644 --- a/js/NavMainComponent.js +++ b/js/NavMainComponent.js @@ -10,6 +10,8 @@ function NavMainComponent ($html, rootWindow, focusManagementService) { }; NavMainComponent.MISSING_ATTR_ERROR = 'A nav link must have a href or data-target attribute'; +NavMainComponent.CLOSE_WITH_ESCAPE = "CLOSE_WITH_ESCAPE"; +NavMainComponent.CLOSE_WITH_CLICK = "CLOSE_WITH_CLICK"; /** * Initialise @@ -27,7 +29,6 @@ NavMainComponent.prototype.init = function () { component.$body = this.$html.find('body'); component.$navMain = this.$html.find('.nav-main'); - component.$contentMain = this.$html.find('.content-main'); component.$brandingLink = this.$html.find('.jadu-branding'); component.$navPrimary = this.$html.find('.nav-primary'); component.$navSecondary = this.$html.find('.nav-secondary'); @@ -82,9 +83,11 @@ NavMainComponent.prototype.init = function () { } }); - // Close navs on main content click - component.$contentMain.on('click', function () { - component.closeNavs($(this)); + // Close navs when element outside of nav is clicked + component.$body.find('.toolbar, .content-main, .footer').on('click', function () { + if (component.isNavOpen()) { + component.closeNavs($(this)); + } }); // Open secondary nav on primary nav item click @@ -126,11 +129,11 @@ NavMainComponent.prototype.init = function () { this.$html.on('keydown', function (event) { if (event.keyCode === 27) { if (component.$navQuaternary.hasClass('is-open')) { - component.closeQuaternaryNav(); + component.closeQuaternaryNav({ type: NavMainComponent.CLOSE_WITH_ESCAPE }); } else if (component.$navTertiary.hasClass('is-open')) { component.closeTertiaryNav(); } else if (component.$navSecondary.hasClass('is-open')) { - component.closeSecondaryNav(); + component.closeSecondaryNav({ type: NavMainComponent.CLOSE_WITH_ESCAPE }); } else if (isMobile && component.$body.hasClass('open-nav')) { component.showMobileNav(false) } @@ -138,6 +141,27 @@ NavMainComponent.prototype.init = function () { }); } +/** + * Check if any navs are open + * @returns {boolean} + */ +NavMainComponent.prototype.isNavOpen = function () { + var component = this, + isMobile = !component.window.matchMedia('(min-width: 992px)').matches; + + if (component.$navQuaternary.hasClass('is-open')) { + return true; + } else if (component.$navTertiary.hasClass('is-open')) { + return true; + } else if (component.$navSecondary.hasClass('is-open')) { + return true; + } else if (isMobile && component.$body.hasClass('open-nav')) { + return true; + } else { + return false; + } +} + /** * Unto the tabindex if the main nav is in responsive mode * This maintains the tab order to ensure WCAG compliance @@ -157,7 +181,7 @@ NavMainComponent.prototype.manageTabIndexes = function () { /** * Open secondary navigation, close all other navs and highlight primary nav item parent - * @param {jQuery} $linkClicked - the element clicked to open secondary nav + * @param {jQuery} $triggeringElement - the element clicked to open secondary nav * @param {Event} event - click event for the primary nav link */ NavMainComponent.prototype.openSecondaryNav = function ($triggeringElement, event) { @@ -235,7 +259,7 @@ NavMainComponent.prototype.closeNavs = function ($linkClicked) { $linkParent = $linkClicked.closest('.nav-flyout'); if ($linkParent.hasClass('nav-secondary')) { - component.closeSecondaryNav(); + component.closeSecondaryNav({ type: NavMainComponent.CLOSE_WITH_CLICK, trigger: $linkClicked }); } else if ($linkParent.hasClass('nav-tertiary')) { @@ -243,20 +267,22 @@ NavMainComponent.prototype.closeNavs = function ($linkClicked) { } else if ($linkParent.hasClass('nav-quaternary')) { - component.closeQuaternaryNav(); + component.closeQuaternaryNav({ type: NavMainComponent.CLOSE_WITH_CLICK, trigger: $linkClicked }); } + // like a body click or something outside of navs else { - component.closeSecondaryNav(); + component.closeSecondaryNav({ type: NavMainComponent.CLOSE_WITH_CLICK, trigger: $linkClicked }); component.closeTertiaryNav(); - component.closeQuaternaryNav(); + component.closeQuaternaryNav({ type: NavMainComponent.CLOSE_WITH_CLICK, trigger: $linkClicked }); } } /** * Close secondary navigation + * @param {Object} [action] */ -NavMainComponent.prototype.closeSecondaryNav = function () { +NavMainComponent.prototype.closeSecondaryNav = function (action) { var component = this; component.$navMain.removeClass('is-open'); @@ -266,8 +292,23 @@ NavMainComponent.prototype.closeSecondaryNav = function () { component.$navMain.find('.nav-item.is-active').removeClass('is-active'); component.$navSecondary.find('.nav-list').removeClass('is-active'); - if (component.focusManagementService.hasStoredElement()) { - component.focusManagementService.returnFocusToElement(); + if (action === undefined) { + return; + } + + switch (action.type) { + case NavMainComponent.CLOSE_WITH_ESCAPE: + if (component.focusManagementService.hasStoredElement()) { + component.focusManagementService.returnFocusToElement(); + } + break; + case NavMainComponent.CLOSE_WITH_CLICK: + if (action.trigger.parents('.nav-secondary').length > 0) { + if (component.focusManagementService.hasStoredElement()) { + component.focusManagementService.returnFocusToElement(); + } + } + break; } } @@ -288,22 +329,35 @@ NavMainComponent.prototype.closeTertiaryNav = function () { /** * Close quaternary navigation + * @param {Object} [action] */ -NavMainComponent.prototype.closeQuaternaryNav = function () { +NavMainComponent.prototype.closeQuaternaryNav = function (action) { var component = this; component.$navQuaternary.removeClass('is-open'); component.$navQuaternary.find('.nav-list.is-active').removeClass('is-active'); + component.$navTertiary.find('[aria-expanded=true]').attr('aria-expanded', 'false'); - if (component.focusManagementService.hasStoredElement()) { - component.focusManagementService.returnFocusToElement(); + if (action === undefined) { + return; } - // Reset aria-expanded on tertiary link - component.$navTertiary.find('[aria-expanded=true]').attr('aria-expanded', 'false'); + switch (action.type) { + case NavMainComponent.CLOSE_WITH_ESCAPE: + if (component.focusManagementService.hasStoredElement()) { + component.focusManagementService.returnFocusToElement(); + } + break; + case NavMainComponent.CLOSE_WITH_CLICK: + if (action.trigger.parents('.nav-quaternary').length > 0) { + if (component.focusManagementService.hasStoredElement()) { + component.focusManagementService.returnFocusToElement(); + } + } + break; + } } - /** * Toggle mobile navigation */ diff --git a/tests/js/web/NavMainComponentTest.js b/tests/js/web/NavMainComponentTest.js index 480df96aac..4da483183f 100644 --- a/tests/js/web/NavMainComponentTest.js +++ b/tests/js/web/NavMainComponentTest.js @@ -871,4 +871,46 @@ describe('NavMainComponent', function () { expect(this.$body.hasClass('open-nav')).to.be.false; }); }); + + describe('isNavOpen()', function () { + it('should return true if quaternary nav is open', function () { + this.navMainComponent.init(); + + this.$html.find('.nav-quaternary').addClass('is-open'); + + expect(this.navMainComponent.isNavOpen()).to.be.true; + }); + + it('should return true if tertiary nav is open', function () { + this.navMainComponent.init(); + + this.$html.find('.nav-tertiary').addClass('is-open'); + + expect(this.navMainComponent.isNavOpen()).to.be.true; + }); + + it('should return true if secondary nav is open', function () { + this.navMainComponent.init(); + + this.$html.find('.nav-secondary').addClass('is-open'); + + expect(this.navMainComponent.isNavOpen()).to.be.true; + }); + + it('should return true if mobile nav is open', function () { + this.window.matchMedia.returns({matches: false}); + this.navMainComponent.init(); + this.clickEvent = $.Event('click'); + + this.$mobileMenuButton.trigger(this.clickEvent); + + expect(this.navMainComponent.isNavOpen()).to.be.true; + }); + + it('should return false if all navs are closed', function () { + this.navMainComponent.init(); + + expect(this.navMainComponent.isNavOpen()).to.be.false; + }); + }); }); From d6ed3d2dc6b9cbb78d7bb6d5068af347dd29d499 Mon Sep 17 00:00:00 2001 From: James Jacobs <jamesjacobs1@mac.com> Date: Mon, 7 Sep 2020 08:27:37 +0100 Subject: [PATCH 03/15] Fix disapearing help button issue --- js/HelpTextComponent.js | 8 ++--- stylesheets/_component.tab-help.scss | 26 +------------- tests/js/web/HelpTextComponentTest.js | 51 +++++++++++++-------------- 3 files changed, 30 insertions(+), 55 deletions(-) diff --git a/js/HelpTextComponent.js b/js/HelpTextComponent.js index 08a5075474..63b98309f3 100644 --- a/js/HelpTextComponent.js +++ b/js/HelpTextComponent.js @@ -87,11 +87,11 @@ HelpTextComponent.prototype.updateHelpSidebar = function () { var component = this, $activeTabContainer = component.$html.find('.tab__pane.is-active .tab__container'), activeTabSideBarContentHtml = component.$html.find('.tab__pane.is-active .tab__sidebar').html(), - $mobileToggleHelpButton = $('<button class="show-page-help js-show-page-help"><i class="icon-question-sign" aria-hidden="true"></i><span class="hide">Show on-page help</span></button>'), - $mobileToggleContainer = component.$html.find('.toolbar'), + $mobileToggleHelpButton = $('<button type="button" class="btn show-page-help js-show-page-help">Show Page Help</button>'), + $mobileToggleContainer = component.$html.find('.tab__pane.is-active .tab__content'), $tabHelp = component.$html.find('.tab-help'), isMobile, - mobileCloseHelpButton = '<button class="close-page-help js-close-page-help"><i class="icon-remove-sign" aria-hidden="true"></i><span class="hide">Close on-page help</span></button>'; + mobileCloseHelpButton = '<button type="button" class="close-page-help js-close-page-help"><i class="icon-remove-sign" aria-hidden="true"></i><span class="hide">Close on-page help</span></button>'; // Check if active tab has help text if (activeTabSideBarContentHtml && activeTabSideBarContentHtml.length > 0) { @@ -111,7 +111,7 @@ HelpTextComponent.prototype.updateHelpSidebar = function () { // If mobile help button doesn't already exist add it if help text exists if (!$mobileToggleContainer.find('.js-show-page-help').length) { - $mobileToggleHelpButton.appendTo($mobileToggleContainer); + $mobileToggleHelpButton.prependTo($mobileToggleContainer); } // Add class used for setting desktop column widths diff --git a/stylesheets/_component.tab-help.scss b/stylesheets/_component.tab-help.scss index 06b365b12e..355fe269c7 100644 --- a/stylesheets/_component.tab-help.scss +++ b/stylesheets/_component.tab-help.scss @@ -62,35 +62,11 @@ $nav-color-dark: darken(color(jadu-blue, dark), 5%) !default; } .show-page-help { - background-color: transparent; - border: 0; - color: color(gray, dark); - display: inline-block; - font-size: 28px; - line-height: normal; - margin-top: -45px; - padding: 0; - position: absolute; - text-decoration: none; - transition: all 100ms ease-in; - right: 20px; + margin-bottom: $margin-large-vertical; @include respond-min($screen-desktop) { display: none; } - - &:hover, - &.is-open { - color: $nav-color-dark; - } - - &:active { - color: darken($nav-color-dark, 10%); - } - - &:focus { - @include pulsar-button-focused; - } } .close-page-help { diff --git a/tests/js/web/HelpTextComponentTest.js b/tests/js/web/HelpTextComponentTest.js index 682a347fa2..33089186a1 100644 --- a/tests/js/web/HelpTextComponentTest.js +++ b/tests/js/web/HelpTextComponentTest.js @@ -14,7 +14,6 @@ describe('HelpTextComponent', function() { this.$document = $('<div></div>').appendTo(this.$window); this.$html = $('<html></html>').appendTo(this.$document); this.$body = $('<body></body>').appendTo(this.$html); - this.$toolbar = $('<div class="toolbar"></div>').appendTo(this.$body); this.$tabHelpContainer = $('<div class="tab-help-container"></div>').appendTo(this.$body); this.$tabHelp = $('<div class="tab-help"></div>').appendTo(this.$tabHelpContainer); this.$contentMain = $('<div class="content-main"></div>').appendTo(this.$body); @@ -46,15 +45,15 @@ describe('HelpTextComponent', function() { }); it('should copy the active tabs sidebar contents to the tab-help container', function() { - expect(this.$tabHelp.html()).to.equal('<button class="close-page-help js-close-page-help" tabindex="-1"><i class="icon-remove-sign" aria-hidden="true"></i><span class="hide">Close on-page help</span></button>Some help text <a href="#" tabindex="-1">link</a>'); + expect(this.$tabHelp.html()).to.equal('<button type="button" class="close-page-help js-close-page-help" tabindex="-1"><i class="icon-remove-sign" aria-hidden="true"></i><span class="hide">Close on-page help</span></button>Some help text <a href="#" tabindex="-1">link</a>'); }); it('should add the help-close button to the tab-help container', function() { expect(this.$tabHelp.find('.js-close-page-help').length).to.equal(1); }); - it('should add the help toggle button to the toolbar', function() { - expect(this.$toolbar.find('.js-show-page-help').length).to.equal(1); + it('should add the help toggle button to .tab__content', function() { + expect(this.$tabContent.find('.js-show-page-help').length).to.equal(1); }); it('should add tabindex="-1" to all links and buttons', function () { @@ -73,45 +72,45 @@ describe('HelpTextComponent', function() { }); it('should prevent the default behaviour', function () { - this.$toolbar.find('.js-show-page-help').trigger(this.clickEvent); + this.$tabContent.find('.js-show-page-help').trigger(this.clickEvent); expect(this.clickEvent.isDefaultPrevented()).to.be.true; }); it('should stop propagation of the click event', function () { - this.$toolbar.find('.js-show-page-help').trigger(this.clickEvent); + this.$tabContent.find('.js-show-page-help').trigger(this.clickEvent); expect(this.clickEvent.isPropagationStopped()).to.be.true; }); it('should open the side menu', function () { - this.$toolbar.find('.js-show-page-help').trigger(this.clickEvent); + this.$tabContent.find('.js-show-page-help').trigger(this.clickEvent); expect(this.$html.hasClass('open-help')).to.be.true; }); it('should add the is-open class to the button ', function () { - this.$toolbar.find('.js-show-page-help').trigger(this.clickEvent); + this.$tabContent.find('.js-show-page-help').trigger(this.clickEvent); - expect(this.$toolbar.find('.js-show-page-help').hasClass('is-open')).to.be.true; + expect(this.$tabContent.find('.js-show-page-help').hasClass('is-open')).to.be.true; }); it('should remove the aria-hidden attribute from the tab-help-container', function () { - this.$toolbar.find('.js-show-page-help').trigger(this.clickEvent); + this.$tabContent.find('.js-show-page-help').trigger(this.clickEvent); expect(this.$tabHelpContainer.attr('aria-hidden')).to.be.undefined; }); it('move focus to the close button', function () { sinon.spy(this.helpTextComponent, 'toggleHelpSidebar'); - this.$toolbar.find('.js-show-page-help').trigger(this.clickEvent); + this.$tabContent.find('.js-show-page-help').trigger(this.clickEvent); this.$tabHelpContainer.trigger('transitionend'); expect(this.helpTextComponent.toggleHelpSidebar).to.have.been.called; }); it('should remove tabindex="-1" from all links and buttons', function () { - this.$toolbar.find('.js-show-page-help').trigger(this.clickEvent); + this.$tabContent.find('.js-show-page-help').trigger(this.clickEvent); expect(this.$tabHelp.find('a').attr('tabindex')).to.be.undefined; }); @@ -125,43 +124,43 @@ describe('HelpTextComponent', function() { this.helpTextComponent.updateHelpSidebar(); this.clickEvent = $.Event('click'); this.$html.addClass('open-help'); - this.$toolbar.find('.js-show-page-help').addClass('is-open'); + this.$tabContent.find('.js-show-page-help').addClass('is-open'); }); it('should close the side menu', function () { - this.$toolbar.find('.js-show-page-help').trigger(this.clickEvent); + this.$tabContent.find('.js-show-page-help').trigger(this.clickEvent); expect(this.$html.hasClass('open-help')).to.be.false; }); it('should remove the is-open class from the help button', function () { - this.$toolbar.find('.js-show-page-help').trigger(this.clickEvent); + this.$tabContent.find('.js-show-page-help').trigger(this.clickEvent); - expect(this.$toolbar.find('.js-show-page-help').hasClass('is-open')).to.be.false; + expect(this.$tabContent.find('.js-show-page-help').hasClass('is-open')).to.be.false; }); it('should add the hide class to the tab-help-container', function () { - this.$toolbar.find('.js-show-page-help').trigger(this.clickEvent); + this.$tabContent.find('.js-show-page-help').trigger(this.clickEvent); this.$tabHelpContainer.trigger('transitionend'); expect(this.$tabHelpContainer.hasClass('hide')).to.be.true; }); it('should add aria-hidden attribute to the tab-help-container', function () { - this.$toolbar.find('.js-show-page-help').trigger(this.clickEvent); + this.$tabContent.find('.js-show-page-help').trigger(this.clickEvent); this.$tabHelpContainer.trigger('transitionend'); expect(this.$tabHelpContainer.attr('aria-hidden')).to.equal('true'); }); it('should add the hide class to the tab-help-container if lt-ie10', function () { this.$html.addClass('lt-ie10'); - this.$toolbar.find('.js-show-page-help').trigger(this.clickEvent); + this.$tabContent.find('.js-show-page-help').trigger(this.clickEvent); expect(this.$tabHelpContainer.hasClass('hide')).to.be.true; }); it('should add aria-hidden attribute to the tab-help-container', function () { this.$html.addClass('lt-ie10'); - this.$toolbar.find('.js-show-page-help').trigger(this.clickEvent); + this.$tabContent.find('.js-show-page-help').trigger(this.clickEvent); expect(this.$tabHelpContainer.attr('aria-hidden')).to.equal('true'); }); @@ -187,7 +186,7 @@ describe('HelpTextComponent', function() { }); it('should stop propagation of the click event', function () { - this.$toolbar.find('.js-show-page-help').trigger(this.clickEvent); + this.$tabContent.find('.js-show-page-help').trigger(this.clickEvent); expect(this.clickEvent.isPropagationStopped()).to.be.true; }); @@ -201,11 +200,11 @@ describe('HelpTextComponent', function() { }); it('should remove the is-open class from the help button', function () { - this.$toolbar.find('.js-show-page-help').addClass('is-open'); + this.$tabContent.find('.js-show-page-help').addClass('is-open'); this.$tabHelp.find('.js-close-page-help').trigger(this.clickEvent); - expect(this.$toolbar.find('.js-show-page-help').hasClass('is-open')).to.be.false; + expect(this.$tabContent.find('.js-show-page-help').hasClass('is-open')).to.be.false; }); it('should add the hide class from the tab-help-container', function () { @@ -253,7 +252,7 @@ describe('HelpTextComponent', function() { this.helpTextComponent.updateHelpSidebar(); this.clickEvent = $.Event('click'); this.$html.addClass('open-help'); - this.$toolbar.find('.js-show-page-help').addClass('is-open'); + this.$tabContent.find('.js-show-page-help').addClass('is-open'); }); it('should close the help side bar', function () { @@ -265,7 +264,7 @@ describe('HelpTextComponent', function() { it('should remove the is-open class from the mobile help button', function () { this.$tabContentLink.trigger(this.clickEvent); - expect(this.$toolbar.find('.js-show-page-help').hasClass('is-open')).to.be.false; + expect(this.$tabContent.find('.js-show-page-help').hasClass('is-open')).to.be.false; }); it('should add the hide class from the tab-help-container', function () { @@ -298,7 +297,7 @@ describe('HelpTextComponent', function() { }); it('should copy the active tabs sidebar contents to the tab-help container', function() { - expect(this.$tabHelp.html()).to.equal('<button class="close-page-help js-close-page-help" tabindex="-1"><i class="icon-remove-sign" aria-hidden="true"></i><span class="hide">Close on-page help</span></button>Some help text <a href="#" tabindex="-1">link</a>'); + expect(this.$tabHelp.html()).to.equal('<button type="button" class="close-page-help js-close-page-help" tabindex="-1"><i class="icon-remove-sign" aria-hidden="true"></i><span class="hide">Close on-page help</span></button>Some help text <a href="#" tabindex="-1">link</a>'); }); it('should add the help-close button to the tab-help container', function() { From b718ed3685dce406930d4638cdbc8d89527fa825 Mon Sep 17 00:00:00 2001 From: James Jacobs <jamesjacobs1@mac.com> Date: Mon, 7 Sep 2020 08:27:56 +0100 Subject: [PATCH 04/15] Fix issue where flash appeared over dropdowns --- stylesheets/_config.variables.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stylesheets/_config.variables.scss b/stylesheets/_config.variables.scss index 7dc355e427..fc86580693 100644 --- a/stylesheets/_config.variables.scss +++ b/stylesheets/_config.variables.scss @@ -220,8 +220,8 @@ $zindex-tour: 1000 !default; $zindex-footer: 1020 !default; $zindex-tooltip: 1055 !default; $zindex-sticky: 1060 !default; -$zindex-dropdown: 1060 !default; -$zindex-flash: 1065 !default; +$zindex-flash: 1064 !default; +$zindex-dropdown: 1065 !default; $zindex-nav-secondary: 1070 !default; $zindex-nav: 1080 !default; $zindex-popover: 1090 !default; From 48edf1cdea2f2aa01b9943d9532fd53fb34db02d Mon Sep 17 00:00:00 2001 From: James Jacobs <jamesjacobs1@mac.com> Date: Mon, 7 Sep 2020 08:28:10 +0100 Subject: [PATCH 05/15] Make search 100% width on mobile --- stylesheets/_component.search.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/stylesheets/_component.search.scss b/stylesheets/_component.search.scss index e531dc00cb..837cb89d08 100644 --- a/stylesheets/_component.search.scss +++ b/stylesheets/_component.search.scss @@ -6,7 +6,6 @@ flex-grow: 1; height: 32px; margin-bottom: 10px; - margin-right: 5px; position: relative; @include respond-min($screen-tablet) { From c71ee42f23fdc3cd5b93a7a8d0e10eb53870257c Mon Sep 17 00:00:00 2001 From: James Jacobs <jamesjacobs1@mac.com> Date: Mon, 7 Sep 2020 08:28:29 +0100 Subject: [PATCH 06/15] Fix dropdown divider styles --- stylesheets/_component.dropdowns.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stylesheets/_component.dropdowns.scss b/stylesheets/_component.dropdowns.scss index 97ce0d5257..81aea7d1f3 100644 --- a/stylesheets/_component.dropdowns.scss +++ b/stylesheets/_component.dropdowns.scss @@ -53,12 +53,13 @@ // Dividers (basically an hr) within the dropdown .divider { + background-color: $dropdown-divider-bg; display: block; height: 1px; margin: ($line-height-base / 4) 0; min-width: 160px; overflow: hidden; - background-color: $dropdown-divider-bg; + padding: 0; } > li { From 9f7466d44cf23ae7726197967fe4f2befda4bd06 Mon Sep 17 00:00:00 2001 From: James Jacobs <jamesjacobs1@mac.com> Date: Mon, 7 Sep 2020 10:56:55 +0100 Subject: [PATCH 07/15] Fix IE11 flexbox issue causing layout to shift under main nav --- stylesheets/_component.toolbar.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/stylesheets/_component.toolbar.scss b/stylesheets/_component.toolbar.scss index 0beed5a047..4f3bc63ff6 100644 --- a/stylesheets/_component.toolbar.scss +++ b/stylesheets/_component.toolbar.scss @@ -5,6 +5,10 @@ flex-wrap: wrap; justify-content: space-between; padding: 12px 20px 0; + + .main-title { + flex: 1 1 100%; + } } .toolbar-actions { From e47c27f94b38a0b2a80b32f6af06a345528f8bf3 Mon Sep 17 00:00:00 2001 From: James Jacobs <jamesjacobs1@mac.com> Date: Mon, 7 Sep 2020 11:40:35 +0100 Subject: [PATCH 08/15] Move skip link to landmark to prevent a11y tool failures --- views/pulsar/layouts/base.html.twig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/views/pulsar/layouts/base.html.twig b/views/pulsar/layouts/base.html.twig index c321aef088..9347845dbe 100644 --- a/views/pulsar/layouts/base.html.twig +++ b/views/pulsar/layouts/base.html.twig @@ -53,7 +53,9 @@ {% block pageStyle %}{% endblock %} </head> <body class="language-html"> - <a class="skip-link" href="#skip-target" tabindex="1">Skip to main content</a> + <div role="complementary" aria-label="skip to main content"> + <a class="skip-link" href="#skip-target" tabindex="1">Skip to main content</a> + </div> {% block body %} <div class="container" id="top"> From 45382e56a6513f4766d85ed8fe60e75083fa87d6 Mon Sep 17 00:00:00 2001 From: James Jacobs <jamesjacobs1@mac.com> Date: Mon, 7 Sep 2020 11:42:59 +0100 Subject: [PATCH 09/15] Remove aria-label from logo link to stop a11y tool failure on link text not matching label --- views/pulsar/v2/helpers/nav.html.twig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/views/pulsar/v2/helpers/nav.html.twig b/views/pulsar/v2/helpers/nav.html.twig index b3451d1615..b2b51956ff 100644 --- a/views/pulsar/v2/helpers/nav.html.twig +++ b/views/pulsar/v2/helpers/nav.html.twig @@ -5,7 +5,7 @@ <nav role="navigation" class="nav-main" aria-label="primary" id="aria-main-nav"> <div class="nav-primary t-nav-primary"> - <a href="{{ options.brand_link|default('#') }}" class="jadu-branding" aria-label="Home"> + <a href="{{ options.brand_link|default('#') }}" class="jadu-branding"> <span class="jadu-logomark"><span class="hide">Return to home</span></span> <span class="jadu-wordmark">Jadu</span> </a> @@ -131,11 +131,11 @@ ) }} > - <{{ attributes(options|only('aria-label href data-toggle data-target'), { 'tag': tag }) }} - - class="nav-link t-nav-link" - {% if options.class is defined and 'is-active' in options.class %} aria-current="page" {% endif %} - {% if options.href starts with '#' or tag == 'button' %} aria-haspopup="true" aria-expanded="false" + <{{ attributes(options|only('aria-label href data-toggle data-target'), { 'tag': tag }) }} + + class="nav-link t-nav-link" + {% if options.class is defined and 'is-active' in options.class %} aria-current="page" {% endif %} + {% if options.href starts with '#' or tag == 'button' %} aria-haspopup="true" aria-expanded="false" {% if aria_controls is not null %}aria-controls="{{ aria_controls }}"{% endif %} {% endif %} > From 8382c2ee3eb7dd4d17c62315cc6b446fc78562fd Mon Sep 17 00:00:00 2001 From: James Jacobs <jamesjacobs1@mac.com> Date: Thu, 17 Sep 2020 11:18:37 +0100 Subject: [PATCH 10/15] Removed old CXM specific styles from piano sass partial --- stylesheets/_component.piano.scss | 77 ------------------------------- 1 file changed, 77 deletions(-) diff --git a/stylesheets/_component.piano.scss b/stylesheets/_component.piano.scss index f4f72e0a71..1e1a1ed935 100644 --- a/stylesheets/_component.piano.scss +++ b/stylesheets/_component.piano.scss @@ -139,25 +139,6 @@ $piano-bg: #fafafa; list-style: none; } - .history { - background-color: color(new); - border-bottom: 1px solid color(border); - border-top: 1px solid color(border); - color: #989699; - cursor: pointer; - display: block; - margin: 1em 0; - position: relative; - text-align: center; - - span { - background-color: color(new); - padding: 0 20px; - position: relative; - z-index: 1; - } - } - h4 { font-size: $font-size-base; @@ -221,56 +202,10 @@ $piano-bg: #fafafa; display: block; } -.message-old.message--open { - border-bottom: 3px double $gray; -} - .message { overflow: hidden; padding: 1em $gutter-width 0; - &.message--open { - .message-body { - height: auto; - } - } - - &.message--closed { - color: lighten($text-color, 2%); - padding: 10px; - - .message-body { - border-top: 1px dashed color(border); - margin-top: .5em; - } - - .message-body { - > * { - display: none; - } - } - - &:hover { - cursor: pointer; - } - - &:nth-of-type(odd) { - background-color: lighten($gray, 17%); - - &:hover { - background-color: lighten($gray, 16%); - } - } - - &:nth-of-type(even) { - background-color: lighten($gray, 15%); - - &:hover { - background-color: lighten($gray, 14%); - } - } - } - .preview > p { display: inline; } @@ -329,15 +264,3 @@ $piano-bg: #fafafa; .setting__actions { padding: 10px; } - -.piano-wrapper { - - .tab__content { - display: table; - } - - .tabs__content { - display: table; - width: 100%; - } -} From fe8f66500eb6c42ad2572987ffb57733d15509aa Mon Sep 17 00:00:00 2001 From: James Jacobs <jamesjacobs1@mac.com> Date: Thu, 24 Sep 2020 08:31:28 +0100 Subject: [PATCH 11/15] Fix focus styles of dropdown danger link --- stylesheets/_component.dropdowns.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/stylesheets/_component.dropdowns.scss b/stylesheets/_component.dropdowns.scss index 81aea7d1f3..cff0396cef 100644 --- a/stylesheets/_component.dropdowns.scss +++ b/stylesheets/_component.dropdowns.scss @@ -313,6 +313,7 @@ &:focus { @include pulsar-dropdown-item-focused; + background-color: color(background, focus) !important; color: color(black) !important; } From 455cf0346785fae7fe4f96d4dc3335498fcec1a0 Mon Sep 17 00:00:00 2001 From: James Jacobs <jamesjacobs1@mac.com> Date: Mon, 9 Nov 2020 12:22:26 +0000 Subject: [PATCH 12/15] Added extra condition to deal with edge case modals with none of the standard buttons --- js/Modals/ModalFocusService.js | 9 ++++++--- tests/js/web/Modals/ModalFocusServiceTest.js | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/js/Modals/ModalFocusService.js b/js/Modals/ModalFocusService.js index c561d43b82..3e6f44f83f 100644 --- a/js/Modals/ModalFocusService.js +++ b/js/Modals/ModalFocusService.js @@ -19,15 +19,18 @@ class ModalFocusService { /** * Manage focus * If modal has focusable elements in body, focus the first - * if not, focus the X close button - * if for some reason it doesn't have that, focus the cancel button in footer + * If not, focus the X close button + * If for some reason it doesn't have that, focus the cancel button in footer + * As a last resort, search for anything to focus */ if ($modalBodyFocusableElements.length) { $modalBodyFocusableElements.first().trigger('focus'); } else if ($modalCloseButton.length) { $modalCloseButton.trigger('focus'); - } else { + } else if ($modalFooterCancelButton.length) { $modalFooterCancelButton.trigger('focus'); + } else { + $modalFocusableElements.first().trigger('focus'); } /** diff --git a/tests/js/web/Modals/ModalFocusServiceTest.js b/tests/js/web/Modals/ModalFocusServiceTest.js index ff2f589892..3ea6acc1d0 100644 --- a/tests/js/web/Modals/ModalFocusServiceTest.js +++ b/tests/js/web/Modals/ModalFocusServiceTest.js @@ -11,6 +11,7 @@ describe('ModalFocusService', () => { let $closeButton; let $inputButton; let $cancelButton; + let $submitButton; let keyDownEvent; let service; @@ -61,6 +62,7 @@ describe('ModalFocusService', () => { $closeButton = $modal.find('.close'); $inputButton = $modal.find('.input4'); + $submitButton = $modal.find('.input7'); $cancelButton = $modal.find('.input8'); keyDownEvent = $.Event('keydown'); @@ -102,6 +104,18 @@ describe('ModalFocusService', () => { }); }); + describe('When there are no focusable elements, no close X button and no footer button with data-dismiss="modal"', () => { + it('should focus the first interactive element in the modal', () => { + $body.find('.modal__body').empty(); + $body.find('.modal__header .close').remove(); + $body.find('.modal__footer [data-dismiss="modal"]').remove(); + + service.trapFocus($modal, $body); + + expect($submitButton.is(':focus')).to.be.true; + }); + }); + describe('when tab is pressed', () => { it('should focus the first element, when the last element was previously focused', () => { service.trapFocus($modal, $body); From 694d7af5d4a7ca307365f7e350c0dea516896223 Mon Sep 17 00:00:00 2001 From: James Jacobs <jamesjacobs1@mac.com> Date: Mon, 9 Nov 2020 12:23:21 +0000 Subject: [PATCH 13/15] Tooltip v2 slight refactor to add methods useful for ajax content --- js/Tooltips/TooltipListener.js | 64 +++++++++++++++++--- tests/js/web/Tooltips/TooltipListenerTest.js | 53 +++++++++++++++- 2 files changed, 106 insertions(+), 11 deletions(-) diff --git a/js/Tooltips/TooltipListener.js b/js/Tooltips/TooltipListener.js index 60d32d03e7..e773d7aaaa 100644 --- a/js/Tooltips/TooltipListener.js +++ b/js/Tooltips/TooltipListener.js @@ -8,7 +8,7 @@ class TooltipListener { /** * Tooltip listener * @constructor - * @param {jQuery} $html - jQuery wrapper of the html node + * @param {jQuery} $html - jQuery wrapper of the container node * @param {tippy} TippyJS lib * @param {hideAll} Tippy hideAll method */ @@ -16,14 +16,8 @@ class TooltipListener { this.$html = $html; this.tippy = tippy; this.hideAll = hideAll - } - - /** - * Initialise - */ - init () { - this.tippy('[data-tippy-content]', { - + this.tippys = []; + this.tippyConfig = { // Default to not allowing html inside of tooltip allowHTML: false, @@ -47,7 +41,14 @@ class TooltipListener { onMount: this.onMount, onHide: this.onHide, onHidden: this.onHidden - }); + }; + } + + /** + * Initialise + */ + init () { + this.tippys = this.tippy('[data-tippy-content]', this.tippyConfig); // Close ESC button this.$html.on('keydown', (event) => { @@ -57,6 +58,49 @@ class TooltipListener { }); } + /** + * Listen for new elements with tippys, ignore if already instantiated, create tooltip if not + * @param {jQuery} $html - jQuery wrapper of the container node + */ + listen ($html) { + // Only create new tippys + $html.find('[data-tippy-content]').each((index, element) => { + if (this.isInstantiated(element)) { + return; + } + + this.tippys.push(this.tippy(element, this.tippyConfig)); + }); + } + + /** + * Check if element is instantiated + * @param {Element} element - element to check for tippy instance + */ + isInstantiated(element) { + for (const tippy of this.tippys) { + if (tippy.reference === element) { + return true; + } + } + return false; + } + + + /** + * Get tippy instance of element + * @returns {(object|null)} tippy object relating to an element or null + */ + getInstance(element) { + for (const tippy of this.tippys) { + if (tippy.reference === element) { + return tippy; + } + } + + return null; + } + onCreate (instance) { // Remove unnecessary aria-expanded attribute (added by the interactive option - needed for content hover) $(instance.reference).removeAttr('aria-expanded'); diff --git a/tests/js/web/Tooltips/TooltipListenerTest.js b/tests/js/web/Tooltips/TooltipListenerTest.js index 57350c1adc..0e3af7ecb9 100644 --- a/tests/js/web/Tooltips/TooltipListenerTest.js +++ b/tests/js/web/Tooltips/TooltipListenerTest.js @@ -26,7 +26,6 @@ describe('TooltipListener', () => { }); describe('init()', () => { - beforeEach(() => { tooltipListener.init(); }); @@ -35,6 +34,10 @@ describe('TooltipListener', () => { expect(tippyStub).to.have.been.called; }); + it('should call tippy on an elements with data-tippy-content using the listener config', () => { + expect(tippyStub).to.have.been.calledWith('[data-tippy-content]', tooltipListener.tippyConfig); + }); + describe('When the ESC key is pressed', () => { beforeEach(() => { keyDownEvent.keyCode = 27; @@ -47,6 +50,54 @@ describe('TooltipListener', () => { }); }); + describe('listen()', () => { + it('should create a new tippy, if element was not already a tippy instance', () => { + tippyStub.returns([]); + tooltipListener.init(); + + tippyStub.returns('tippy'); + tooltipListener.listen($html); + + expect(tooltipListener.tippys[0]).to.equal('tippy'); + + expect(tippyStub).to.have.been.calledWith('[data-tippy-content]', tooltipListener.tippyConfig); + }); + + it('should not push a new tippy instance, if element is already a tippy instance', () => { + const existingTippyElement = $button[0]; + const existingTippyElementObject = { reference: existingTippyElement }; + + tippyStub.returns([existingTippyElementObject]); + tooltipListener.init(); + tooltipListener.listen($html); + + expect(tooltipListener.tippys).to.have.length(1); + }); + }); + + describe('getInstance()', () => { + it('should return the tippy instance for an element, if one exists', () => { + const existingTippyElement = $button[0]; + const existingTippyElementObject = { reference: existingTippyElement }; + + tippyStub.returns([existingTippyElementObject]); + tooltipListener.init(); + + expect(tooltipListener.getInstance($button[0])).to.equal(existingTippyElementObject) + }); + + it('should return null, if no tippy instance for an element exists', () => { + const existingTippyElement = $button[0]; + const existingTippyElementObject = { reference: existingTippyElement }; + + tippyStub.returns([existingTippyElementObject]); + tooltipListener.init(); + + expect(tooltipListener.getInstance('something')).to.be.null; + }); + + }); + describe('onCreate()', () => { it('should remove the added aria-expanded attribute', () => { let instance = {'reference': $body.find('#button-1')[0]}; From f04af4baa8c63dc2043a95181d21ac4302ab1211 Mon Sep 17 00:00:00 2001 From: James Jacobs <jamesjacobs1@mac.com> Date: Fri, 13 Nov 2020 15:56:39 +0000 Subject: [PATCH 14/15] =?UTF-8?q?House=20keeping=20=F0=9F=A7=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/Tooltips/TooltipListener.js | 1 - views/pulsar/v2/helpers/nav.html.twig | 1 - 2 files changed, 2 deletions(-) diff --git a/js/Tooltips/TooltipListener.js b/js/Tooltips/TooltipListener.js index e773d7aaaa..f0e8cb8352 100644 --- a/js/Tooltips/TooltipListener.js +++ b/js/Tooltips/TooltipListener.js @@ -86,7 +86,6 @@ class TooltipListener { return false; } - /** * Get tippy instance of element * @returns {(object|null)} tippy object relating to an element or null diff --git a/views/pulsar/v2/helpers/nav.html.twig b/views/pulsar/v2/helpers/nav.html.twig index b2b51956ff..02008ff41d 100644 --- a/views/pulsar/v2/helpers/nav.html.twig +++ b/views/pulsar/v2/helpers/nav.html.twig @@ -132,7 +132,6 @@ }} > <{{ attributes(options|only('aria-label href data-toggle data-target'), { 'tag': tag }) }} - class="nav-link t-nav-link" {% if options.class is defined and 'is-active' in options.class %} aria-current="page" {% endif %} {% if options.href starts with '#' or tag == 'button' %} aria-haspopup="true" aria-expanded="false" From 92ebe74d3150a2512fe258d2c20f9b1630bcbe34 Mon Sep 17 00:00:00 2001 From: James Jacobs <jamesjacobs1@mac.com> Date: Mon, 16 Nov 2020 13:31:05 +0000 Subject: [PATCH 15/15] reversed nav close logic --- js/NavMainComponent.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/NavMainComponent.js b/js/NavMainComponent.js index 32edee1fbe..a0c9fb4997 100644 --- a/js/NavMainComponent.js +++ b/js/NavMainComponent.js @@ -149,13 +149,13 @@ NavMainComponent.prototype.isNavOpen = function () { var component = this, isMobile = !component.window.matchMedia('(min-width: 992px)').matches; - if (component.$navQuaternary.hasClass('is-open')) { - return true; - } else if (component.$navTertiary.hasClass('is-open')) { + if (isMobile && component.$body.hasClass('open-nav')) { return true; } else if (component.$navSecondary.hasClass('is-open')) { return true; - } else if (isMobile && component.$body.hasClass('open-nav')) { + } else if (component.$navTertiary.hasClass('is-open')) { + return true; + } else if (component.$navQuaternary.hasClass('is-open')) { return true; } else { return false;