From f89f8a22b0c15c821610bdbba10a76edc3d17652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauro=20Mascarenhas=20de=20Ara=C3=BAjo?= Date: Wed, 7 Dec 2022 18:36:23 -0300 Subject: [PATCH 1/7] fix(slider): slider accessibility Improve slider support to assistive technologies (e.g.: screen readers): - Add "pause" options to slider initialization: - pauseOnFocus: Slider should pause when receive keyboard focus; - pauseOnHover: Slider should pause when hovered by mouse; - New "indicatorLabelFunc": Must generate ARIA labels for indicators; - New eventPause prop: is true if slider is paused by focus/hover event; - Slider must receive id if does not have one; - Sliders are able to receive focus by code; - Non-current slides are also hidden in CSS (also from screen readers); - Add button to indicators: - Update CSS; - Update event handlers. - Indicator click should focus current slide. --- js/slider.js | 146 ++++++++++++++++++++++++++++++++--- sass/components/_slider.scss | 17 +++- 2 files changed, 147 insertions(+), 16 deletions(-) diff --git a/js/slider.js b/js/slider.js index 40d7100d82..d0d4d04709 100644 --- a/js/slider.js +++ b/js/slider.js @@ -5,7 +5,10 @@ indicators: true, height: 400, duration: 500, - interval: 6000 + interval: 6000, + pauseOnFocus: true, + pauseOnHover: true, + indicatorLabelFunc: null // Function which will generate a label for the indicators (ARIA) }; /** @@ -31,9 +34,18 @@ * @prop {Number} [height=400] - height of slider * @prop {Number} [duration=500] - Length in ms of slide transition * @prop {Number} [interval=6000] - Length in ms of slide interval + * @prop {Boolean} [pauseOnFocus=true] - Pauses transition when slider receives keyboard focus + * @prop {Boolean} [pauseOnHover=true] - Pauses transition while mouse hovers the slider */ this.options = $.extend({}, Slider.defaults, options); + // init props + this.interval = null; + this.eventPause = false; + this._hovered = false; + this._focused = false; + this._focusCurrent = false; + // setup this.$slider = this.$el.find('.slides'); this.$slides = this.$slider.children('li'); @@ -49,6 +61,13 @@ this._setSliderHeight(); + // Sets element id if it does not have one + if (this.$slider[0].hasAttribute("id")) this._sliderId = this.$slider[0].getAttribute("id"); + else { + this._sliderId = "slider-" + M.guid(); + this.$slider[0].setAttribute("id", this._sliderId); + } + // Set initial positions of captions this.$slides.find('.caption').each((el) => { this._animateCaptionIn(el, 0); @@ -63,12 +82,19 @@ $(el).attr('src', placeholderBase64); } }); + this.$slides.each((el) => { + // Sets slide as focusable by code + if (!el.hasAttribute("tabindex")) el.setAttribute("tabindex", -1); + // Removes initial visibility from "inactive" slides + el.style.visibility = 'hidden'; + }); this._setupIndicators(); // Show active slide if (this.$active) { this.$active.css('display', 'block'); + this.$active[0].style.visibility = 'visible'; } else { this.$slides.first().addClass('active'); anim({ @@ -77,13 +103,14 @@ duration: this.options.duration, easing: 'easeOutQuad' }); + this.$slides.first()[0].style.visibility = 'visible'; this.activeIndex = 0; this.$active = this.$slides.eq(this.activeIndex); // Update indicators if (this.options.indicators) { - this.$indicators.eq(this.activeIndex).addClass('active'); + this.$indicators.eq(this.activeIndex).children().first().addClass('active'); } } @@ -137,10 +164,23 @@ _setupEventHandlers() { this._handleIntervalBound = this._handleInterval.bind(this); this._handleIndicatorClickBound = this._handleIndicatorClick.bind(this); + this._handleAutoPauseFocusBound = this._handleAutoPauseFocus.bind(this); + this._handleAutoStartFocusBound = this._handleAutoStartFocus.bind(this); + this._handleAutoPauseHoverBound = this._handleAutoPauseHover.bind(this); + this._handleAutoStartHoverBound = this._handleAutoStartHover.bind(this); + + if (this.options.pauseOnFocus){ + this.el.addEventListener('focusin', this._handleAutoPauseFocusBound); + this.el.addEventListener('focusout', this._handleAutoStartFocusBound); + } + if (this.options.pauseOnHover){ + this.el.addEventListener('mouseenter', this._handleAutoPauseHoverBound); + this.el.addEventListener('mouseleave', this._handleAutoStartHoverBound); + } if (this.options.indicators) { this.$indicators.each((el) => { - el.addEventListener('click', this._handleIndicatorClickBound); + el.children[0].addEventListener('click', this._handleIndicatorClickBound); }); } } @@ -149,9 +189,17 @@ * Remove Event Handlers */ _removeEventHandlers() { + if (this.options.pauseOnFocus){ + this.el.removeEventListener('focusin', this._handleAutoPauseFocusBound); + this.el.removeEventListener('focusout', this._handleAutoStartFocusBound); + } + if (this.options.pauseOnHover){ + this.el.removeEventListener('mouseenter', this._handleAutoPauseHoverBound); + this.el.removeEventListener('mouseleave', this._handleAutoStartHoverBound); + } if (this.options.indicators) { this.$indicators.each((el) => { - el.removeEventListener('click', this._handleIndicatorClickBound); + el.children[0].removeEventListener('click', this._handleIndicatorClickBound); }); } } @@ -161,10 +209,51 @@ * @param {Event} e */ _handleIndicatorClick(e) { - let currIndex = $(e.target).index(); + let currIndex = $(e.target).parent().index(); + this._focusCurrent = true; this.set(currIndex); } + /** + * Mouse enter event handler + */ + _handleAutoPauseHover() { + this._hovered = true; + if (this.interval != null){ + this._pause(true); + } + } + + /** + * Focus in event handler + */ + _handleAutoPauseFocus() { + this._focused = true; + if (this.interval != null){ + this._pause(true); + } + } + + /** + * Mouse enter event handler + */ + _handleAutoStartHover() { + this._hovered = false; + if (!(this.options.pauseOnFocus && this._focused) && this.eventPause){ + this.start(); + } + } + + /** + * Focus out leave event handler + */ + _handleAutoStartFocus() { + this._focused = false; + if (!(this.options.pauseOnHover && this._hovered) && this.eventPause){ + this.start(); + } + } + /** * Handle Interval */ @@ -223,8 +312,11 @@ _setupIndicators() { if (this.options.indicators) { this.$indicators = $(''); - this.$slides.each((el, index) => { - let $indicator = $('
  • '); + this.$slides.each((el, i) => { + let label = this.options.indicatorLabelFunc ? this.options.indicatorLabelFunc.call(this, i + 1, i === 0) : `${i + 1}`; + let $indicator = $(`
  • + +
  • `); this.$indicators.append($indicator[0]); }); this.$el.append(this.$indicators[0]); @@ -253,6 +345,10 @@ this.$active = this.$slides.eq(this.activeIndex); let $caption = this.$active.find('.caption'); this.$active.removeClass('active'); + // Enables every slide + this.$slides.each((el) => { + el.style.visibility = 'visible'; + }); anim({ targets: this.$active[0], @@ -269,6 +365,8 @@ duration: 0, easing: 'easeOutQuad' }); + // Disables invisible slides (for assistive technologies) + el.style.visibility = 'hidden'; }); } }); @@ -277,8 +375,14 @@ // Update indicators if (this.options.indicators) { - this.$indicators.eq(this.activeIndex).removeClass('active'); - this.$indicators.eq(index).addClass('active'); + let oActive = this.$indicators.eq(this.activeIndex).children().first()[0]; + let nActive = this.$indicators.eq(index).children().first()[0]; + oActive.classList.remove('active'); + nActive.classList.add('active'); + if (typeof this.options.indicatorLabelFunc === "function"){ + oActive.setAttribute("aria-label", this.options.indicatorLabelFunc.call(this, this.$indicators.eq(this.activeIndex).index(), false)); + nActive.setAttribute("aria-label", this.options.indicatorLabelFunc.call(this, this.$indicators.eq(index).index(), true)); + } } anim({ @@ -299,18 +403,35 @@ }); this.$slides.eq(index).addClass('active'); + if (this._focusCurrent){ + this.$slides.eq(index)[0].focus(); + this._focusCurrent = false; + } this.activeIndex = index; - // Reset interval - this.start(); + // Reset interval, if allowed. This check prevents autostart + // when slider is paused, since it can be changed though indicators. + if (this.interval != null){ + this.start(); + } } } + /** + * "Protected" function which pauses current interval + * @param {boolean} fromEvent Specifies if request came from event + */ + _pause(fromEvent) { + clearInterval(this.interval); + this.eventPause = fromEvent; + this.interval = null; + } + /** * Pause slider interval */ pause() { - clearInterval(this.interval); + this._pause(false); } /** @@ -322,6 +443,7 @@ this._handleIntervalBound, this.options.duration + this.options.interval ); + this.eventPause = false; } /** diff --git a/sass/components/_slider.scss b/sass/components/_slider.scss index 2265cdb020..16d77b7542 100644 --- a/sass/components/_slider.scss +++ b/sass/components/_slider.scss @@ -74,18 +74,27 @@ .indicator-item { display: inline-block; position: relative; - cursor: pointer; height: 16px; width: 16px; margin: 0 12px; + } + + .indicator-item-btn { + &.active { + background-color: $slider-indicator-color; + } + position: absolute; + top: 0; + left: 0; + cursor: pointer; background-color: $slider-bg-color-light; transition: background-color .3s; border-radius: 50%; + border-width: 0; - &.active { - background-color: $slider-indicator-color; - } + width: 100%; + height: 100%; } } From 81290e0474afb7ebc3bf4133b0658e27fb2d4b06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauro=20Mascarenhas=20de=20Ara=C3=BAjo?= Date: Wed, 7 Dec 2022 18:40:56 -0300 Subject: [PATCH 2/7] test(slider): slider accessibility Implement slider tests: - Add HTML test implementation for Slider; - Add jasmine test spec for Slider (js + fixture). --- test/html/slider.html | 106 +++++++++++++++++++++++++++ tests/spec/slider/sliderFixture.html | 36 +++++++++ tests/spec/slider/sliderSpec.js | 78 ++++++++++++++++++++ 3 files changed, 220 insertions(+) create mode 100644 test/html/slider.html create mode 100644 tests/spec/slider/sliderFixture.html create mode 100644 tests/spec/slider/sliderSpec.js diff --git a/test/html/slider.html b/test/html/slider.html new file mode 100644 index 0000000000..7d256c172c --- /dev/null +++ b/test/html/slider.html @@ -0,0 +1,106 @@ + + + + + + + Documentation - Materialize + + + + + + + + + + + + + + +

    Simple slider

    +
    +
      +
    • + First slider image +
      +

      This is our big Tagline!

      +
      Here's our small slogan.
      +
      +
    • +
    • + Second slider image +
      +

      Left Aligned Caption

      +
      Here's our small slogan.
      +
      +
    • +
    • + Third slider image +
      +

      Right Aligned Caption

      +
      Here's our small slogan.
      +
      +
    • +
    • + Fourth slider image +
      +

      This is our big Tagline!

      +
      Here's our small slogan.
      +
      +
    • +
    +
    + +

    Paused slider

    +
    +
      +
    • + First slider image +
      +

      This is our big Tagline!

      +
      Here's our small slogan.
      +
      +
    • +
    • + Second slider image +
      +

      Left Aligned Caption

      +
      Here's our small slogan.
      +
      +
    • +
    • + Third slider image +
      +

      Right Aligned Caption

      +
      Here's our small slogan.
      +
      +
    • +
    • + Fourth slider image +
      +

      This is our big Tagline!

      +
      Here's our small slogan.
      +
      +
    • +
    +
    + + + + + + + + + diff --git a/tests/spec/slider/sliderFixture.html b/tests/spec/slider/sliderFixture.html new file mode 100644 index 0000000000..b72edeae05 --- /dev/null +++ b/tests/spec/slider/sliderFixture.html @@ -0,0 +1,36 @@ +
    +
      +
    • + First slide +
      +

      This is our big Tagline!

      +
      Here's our small slogan.
      +
      +
    • +
    • + Second slide +
      +

      Left Aligned Caption

      +
      Here's our small slogan.
      +
      +
    • +
    • + Third slide +
      +

      Right Aligned Caption

      +
      Here's our small slogan.
      +
      +
    • +
    • + Fourth slide +
      +

      This is our big Tagline!

      +
      Here's our small slogan.
      +
      +
    • +
    +
    \ No newline at end of file diff --git a/tests/spec/slider/sliderSpec.js b/tests/spec/slider/sliderSpec.js new file mode 100644 index 0000000000..72ae0fdac0 --- /dev/null +++ b/tests/spec/slider/sliderSpec.js @@ -0,0 +1,78 @@ +describe("Slider Plugin", () => { + + beforeEach(async () => { + await XloadFixtures(["slider/sliderFixture.html"]); + }); + afterEach(() => { + XunloadFixtures(); + }); + + describe("Slider", () => { + + let slider; + + beforeEach(() => { + slider = M.Slider.init(document.querySelector(".slider"), { + interval: 1000, + pauseOnFocus: true, + indicatorLabelFunc: (idx) => "Slide " + idx + }); + }); + + afterEach(() => { + if (slider) slider.destroy(); + slider = null; + }); + + it("Slider should change after 1 second", (done) => { + const O_INDEX = slider.activeIndex; + setTimeout(() => { + setTimeout(() => { + expect(slider.activeIndex).not.toBe(O_INDEX); + done(); + }, 1500); + }, 1); + }); + + it("Slider should not change if paused", (done) => { + const O_INDEX = slider.activeIndex; + slider.pause(); + setTimeout(() => { + setTimeout(() => { + expect(slider.activeIndex).toBe(O_INDEX); + done(); + }, 2000); + }, 1); + }); + + it("Slider should not change if focused", (done) => { + const O_INDEX = slider.activeIndex; + slider.start(); + slider.el.querySelector("li").focus(); + + setTimeout(() => { + setTimeout(() => { + expect(slider.eventPause).toBe(true); + expect(slider.activeIndex).toBe(O_INDEX); + done(); + }, 2000); + }, 1); + }); + + it("Label of indicators must start with 'Slide '", () => { + expect(Array.from(document.querySelectorAll("button")).map((btn) => + btn.getAttribute("aria-label").startsWith("Slide ") + )).toEqual([true, true, true, true]); + }); + + it("Slider should change current index and focus its respective item on indicator click", () => { + const IDX = 2; + document.querySelectorAll("button")[IDX].click(); + + expect(slider.activeIndex).toBe(IDX); + expect(document.activeElement).toBe(document.querySelectorAll(".slides > li")[IDX]); + }); + + }); + +}); \ No newline at end of file From 731efeefce5bc3b088be38444d09ee7b4da77b23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauro=20Mascarenhas=20de=20Ara=C3=BAjo?= Date: Wed, 7 Dec 2022 19:15:32 -0300 Subject: [PATCH 3/7] docs(slider): slider accessibility - Add description for new initialization options; - Add description for new instance properties; - Add alternative text for images (updating code samples); - Update Slider init code sample to include indicatorLabelFunc example. --- pug/page-contents/media_content.html | 69 +++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 11 deletions(-) diff --git a/pug/page-contents/media_content.html b/pug/page-contents/media_content.html index 8e55057db4..a31c922c71 100644 --- a/pug/page-contents/media_content.html +++ b/pug/page-contents/media_content.html @@ -8,12 +8,12 @@

    Material Box

    Material box is a material design implementation of the Lightbox plugin. When a user clicks on an image that can be enlarged, Material box centers the image and enlarges it in a smooth, non-jarring manner. To dismiss the image, the user can either click on the image again, scroll away, or press the ESC key.

    - + Material Box sample image

    Creating the above image with the effect is as simple as adding a materialboxed class to the image tag.

    
    -    <img class="materialboxed" width="650" src="images/sample-1.jpg">
    +    <img class="materialboxed" width="650" alt="Material Box sample image" src="images/sample-1.jpg">
           

    Initialization

    @@ -189,7 +189,7 @@

    Captions

    data-caption attribute.

    
    -  <img class="materialboxed" data-caption="A picture of a way with a group of trees in a park" width="250" src="images/placeholder/800x400_d.jpg">
    +  <img class="materialboxed" data-caption="A picture of a way with a group of trees in a park" width="250" alt="Sample image for Material Box with caption" src="images/placeholder/800x400_d.jpg">
               

    @@ -204,7 +204,7 @@

    Slider

    • - + Sample image for first slide

      This is our big Tagline!

      @@ -212,7 +212,7 @@
      Here's our small slogan.
    • - + Sample image for second slide

      Left Aligned Caption

      @@ -220,7 +220,7 @@
      Here's our small slogan.
    • - + Sample image for third slide

      Right Aligned Caption

      @@ -228,7 +228,7 @@
      Here's our small slogan.
    • - + Sample image for fourth slide

      This is our big Tagline!

      @@ -243,28 +243,28 @@
      Here's our small slogan.
      <div class="slider"> <ul class="slides"> <li> - <img src="images/placeholder/800x400_a.jpg"> + <img src="images/placeholder/800x400_a.jpg" alt="Sample image for first slide"> <div class="caption center-align"> <h3>This is our big Tagline!</h3> <h5 class="light grey-text text-lighten-3">Here's our small slogan.</h5> </div> </li> <li> - <img src="images/placeholder/800x400_b.jpg"> + <img src="images/placeholder/800x400_b.jpg" alt="Sample image for second slide"> <div class="caption left-align"> <h3>Left Aligned Caption</h3> <h5 class="light grey-text text-lighten-3">Here's our small slogan.</h5> </div> </li> <li> - <img src="images/placeholder/800x400_c.jpg"> + <img src="images/placeholder/800x400_c.jpg" alt="Sample image for third slide"> <div class="caption right-align"> <h3>Right Aligned Caption</h3> <h5 class="light grey-text text-lighten-3">Here's our small slogan.</h5> </div> </li> <li> - <img src="images/placeholder/800x400_d.jpg"> + <img src="images/placeholder/800x400_d.jpg" alt="Sample image for fourth slide"> <div class="caption center-align"> <h3>This is our big Tagline!</h3> <h5 class="light grey-text text-lighten-3">Here's our small slogan.</h5> @@ -281,6 +281,13 @@

      Initialization

      var elems = document.querySelectorAll('.slider'); var instances = M.Slider.init(elems, { // specify options here + indicatorLabelFunc: (idx, current) => { + let label = "Go to slide " + idx; + if (current){ + label = label + " (Current)"; + } + return label; + } }); }); @@ -289,6 +296,13 @@

      Initialization

      $(document).ready(function(){ $('.slider').slider({ // specify options here + indicatorLabelFunc: (idx, curr) => { + let label = "Go to slide " + idx; + if (current){ + label = label + " (Current)"; + } + return label; + } }); }); @@ -330,6 +344,34 @@

      Options

      6000 Set the duration between transitions in ms. + + pauseOnFocus + Boolean + true + If slider should pause when keyboard focus is received. + + + pauseOnHover + Boolean + true + If slider should pause when is hovered by a pointer. + + + indicatorLabelFunc + Function + null + +

      + Function used to generate ARIA label to indicators (for accessibility purposes). +

      +

      + It receives the current index, starting from "1", and a boolean indicating whether it is the current element or not. +

      +

      + If not specified, the generated label is going to be the current index. +

      + +
      @@ -425,6 +467,11 @@

      Properties

      Number Index of current slide. + + eventPause + Boolean + Indicates whether the slider is paused by a focus/mouse event or not. +
      From 5c20ff1a7928baba84fc58cca28187c9bcf596cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauro=20Mascarenhas=20de=20Ara=C3=BAjo?= Date: Sat, 28 Jan 2023 21:31:43 -0300 Subject: [PATCH 4/7] refactor(slider accessibility): adapt code style to guidelines - Add missing indicatorLabelFunc prop to Slider#options; - Replace double quotes string by single quotes; - Replace vanilla JS calls by jQuery functions. --- js/slider.js | 56 ++++++++++++++++++---------- pug/page-contents/media_content.html | 8 ++-- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/js/slider.js b/js/slider.js index d0d4d04709..fba838c65e 100644 --- a/js/slider.js +++ b/js/slider.js @@ -36,6 +36,7 @@ * @prop {Number} [interval=6000] - Length in ms of slide interval * @prop {Boolean} [pauseOnFocus=true] - Pauses transition when slider receives keyboard focus * @prop {Boolean} [pauseOnHover=true] - Pauses transition while mouse hovers the slider + * @prop {Function} [indicatorLabelFunc=null] - Function used to generate ARIA label to indicators (for accessibility purposes). */ this.options = $.extend({}, Slider.defaults, options); @@ -62,10 +63,10 @@ this._setSliderHeight(); // Sets element id if it does not have one - if (this.$slider[0].hasAttribute("id")) this._sliderId = this.$slider[0].getAttribute("id"); + if (this.$slider.attr('id')) this._sliderId = this.$slider.attr('id'); else { - this._sliderId = "slider-" + M.guid(); - this.$slider[0].setAttribute("id", this._sliderId); + this._sliderId = 'slider-' + M.guid(); + this.$slider.attr('id', this._sliderId); } // Set initial positions of captions @@ -93,8 +94,7 @@ // Show active slide if (this.$active) { - this.$active.css('display', 'block'); - this.$active[0].style.visibility = 'visible'; + this.$active.css('display', 'block').css('visibility', 'visible'); } else { this.$slides.first().addClass('active'); anim({ @@ -103,7 +103,7 @@ duration: this.options.duration, easing: 'easeOutQuad' }); - this.$slides.first()[0].style.visibility = 'visible'; + this.$slides.first().css('visibility', 'visible'); this.activeIndex = 0; this.$active = this.$slides.eq(this.activeIndex); @@ -179,9 +179,7 @@ } if (this.options.indicators) { - this.$indicators.each((el) => { - el.children[0].addEventListener('click', this._handleIndicatorClickBound); - }); + this.$indicators.children().on('click', this._handleIndicatorClickBound); } } @@ -313,7 +311,9 @@ if (this.options.indicators) { this.$indicators = $('
        '); this.$slides.each((el, i) => { - let label = this.options.indicatorLabelFunc ? this.options.indicatorLabelFunc.call(this, i + 1, i === 0) : `${i + 1}`; + let label = this.options.indicatorLabelFunc + ? this.options.indicatorLabelFunc.call(this, i + 1, i === 0) + : `${i + 1}`; let $indicator = $(`
      • `); @@ -346,9 +346,7 @@ let $caption = this.$active.find('.caption'); this.$active.removeClass('active'); // Enables every slide - this.$slides.each((el) => { - el.style.visibility = 'visible'; - }); + this.$slides.css('visibility', 'visible'); anim({ targets: this.$active[0], @@ -375,13 +373,33 @@ // Update indicators if (this.options.indicators) { - let oActive = this.$indicators.eq(this.activeIndex).children().first()[0]; - let nActive = this.$indicators.eq(index).children().first()[0]; - oActive.classList.remove('active'); - nActive.classList.add('active'); + let activeIndicator = this.$indicators + .eq(this.activeIndex) + .children() + .first(); + let nextIndicator = this.$indicators + .eq(index) + .children() + .first(); + activeIndicator.removeClass('active'); + nextIndicator.addClass('active'); if (typeof this.options.indicatorLabelFunc === "function"){ - oActive.setAttribute("aria-label", this.options.indicatorLabelFunc.call(this, this.$indicators.eq(this.activeIndex).index(), false)); - nActive.setAttribute("aria-label", this.options.indicatorLabelFunc.call(this, this.$indicators.eq(index).index(), true)); + activeIndicator.attr( + 'aria-label', + this.options.indicatorLabelFunc.call( + this, + this.$indicators.eq(this.activeIndex).index(), + false + ) + ); + nextIndicator.attr( + 'aria-label', + this.options.indicatorLabelFunc.call( + this, + this.$indicators.eq(index).index(), + true + ) + ); } } diff --git a/pug/page-contents/media_content.html b/pug/page-contents/media_content.html index a31c922c71..861b03ea7f 100644 --- a/pug/page-contents/media_content.html +++ b/pug/page-contents/media_content.html @@ -27,7 +27,7 @@

        Initialization

        // Or with jQuery - $(document).ready(function(){ + $(document).ready(function() { $('.materialboxed').materialbox({ // specify options here }); @@ -283,7 +283,7 @@

        Initialization

        // specify options here indicatorLabelFunc: (idx, current) => { let label = "Go to slide " + idx; - if (current){ + if (current) { label = label + " (Current)"; } return label; @@ -293,12 +293,12 @@

        Initialization

        // Or with jQuery - $(document).ready(function(){ + $(document).ready(function() { $('.slider').slider({ // specify options here indicatorLabelFunc: (idx, curr) => { let label = "Go to slide " + idx; - if (current){ + if (current) { label = label + " (Current)"; } return label; From 26bbe22e6f9cd810020df03662ec79665480d3f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauro=20Mascarenhas=20de=20Ara=C3=BAjo?= Date: Sat, 28 Jan 2023 21:40:37 -0300 Subject: [PATCH 5/7] refactor(slider accessibility): adapt code style to guidelines - Replace double quotes string by single quotes; - Replace vanilla JS calls by jQuery functions. --- js/slider.js | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/js/slider.js b/js/slider.js index fba838c65e..55eb41a63a 100644 --- a/js/slider.js +++ b/js/slider.js @@ -85,7 +85,7 @@ }); this.$slides.each((el) => { // Sets slide as focusable by code - if (!el.hasAttribute("tabindex")) el.setAttribute("tabindex", -1); + if (!el.hasAttribute('tabindex')) el.setAttribute('tabindex', -1); // Removes initial visibility from "inactive" slides el.style.visibility = 'hidden'; }); @@ -169,11 +169,11 @@ this._handleAutoPauseHoverBound = this._handleAutoPauseHover.bind(this); this._handleAutoStartHoverBound = this._handleAutoStartHover.bind(this); - if (this.options.pauseOnFocus){ + if (this.options.pauseOnFocus) { this.el.addEventListener('focusin', this._handleAutoPauseFocusBound); this.el.addEventListener('focusout', this._handleAutoStartFocusBound); } - if (this.options.pauseOnHover){ + if (this.options.pauseOnHover) { this.el.addEventListener('mouseenter', this._handleAutoPauseHoverBound); this.el.addEventListener('mouseleave', this._handleAutoStartHoverBound); } @@ -187,18 +187,16 @@ * Remove Event Handlers */ _removeEventHandlers() { - if (this.options.pauseOnFocus){ + if (this.options.pauseOnFocus) { this.el.removeEventListener('focusin', this._handleAutoPauseFocusBound); this.el.removeEventListener('focusout', this._handleAutoStartFocusBound); } - if (this.options.pauseOnHover){ + if (this.options.pauseOnHover) { this.el.removeEventListener('mouseenter', this._handleAutoPauseHoverBound); this.el.removeEventListener('mouseleave', this._handleAutoStartHoverBound); } if (this.options.indicators) { - this.$indicators.each((el) => { - el.children[0].removeEventListener('click', this._handleIndicatorClickBound); - }); + this.$indicators.children().off('click', this._handleIndicatorClickBound); } } @@ -217,7 +215,7 @@ */ _handleAutoPauseHover() { this._hovered = true; - if (this.interval != null){ + if (this.interval != null) { this._pause(true); } } @@ -227,7 +225,7 @@ */ _handleAutoPauseFocus() { this._focused = true; - if (this.interval != null){ + if (this.interval != null) { this._pause(true); } } @@ -237,7 +235,7 @@ */ _handleAutoStartHover() { this._hovered = false; - if (!(this.options.pauseOnFocus && this._focused) && this.eventPause){ + if (!(this.options.pauseOnFocus && this._focused) && this.eventPause) { this.start(); } } @@ -247,7 +245,7 @@ */ _handleAutoStartFocus() { this._focused = false; - if (!(this.options.pauseOnHover && this._hovered) && this.eventPause){ + if (!(this.options.pauseOnHover && this._hovered) && this.eventPause) { this.start(); } } @@ -421,7 +419,7 @@ }); this.$slides.eq(index).addClass('active'); - if (this._focusCurrent){ + if (this._focusCurrent) { this.$slides.eq(index)[0].focus(); this._focusCurrent = false; } @@ -429,7 +427,7 @@ // Reset interval, if allowed. This check prevents autostart // when slider is paused, since it can be changed though indicators. - if (this.interval != null){ + if (this.interval != null) { this.start(); } } From 6b2ec14d226296263b13c6e9ac43ee73e2683d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauro=20Mascarenhas=20de=20Ara=C3=BAjo?= Date: Thu, 16 Feb 2023 15:27:12 -0300 Subject: [PATCH 6/7] style(slider): remove extra spaces --- js/slider.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/slider.js b/js/slider.js index 55eb41a63a..fa9e27f059 100644 --- a/js/slider.js +++ b/js/slider.js @@ -371,11 +371,11 @@ // Update indicators if (this.options.indicators) { - let activeIndicator = this.$indicators + let activeIndicator = this.$indicators .eq(this.activeIndex) .children() .first(); - let nextIndicator = this.$indicators + let nextIndicator = this.$indicators .eq(index) .children() .first(); From df94e9ecd82c9fac7555efa466846cb4b83aedbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauro=20Mascarenhas=20de=20Ara=C3=BAjo?= Date: Thu, 16 Feb 2023 15:29:07 -0300 Subject: [PATCH 7/7] docs(slider): identify indicatorLabelFunc as optional Mark "indicatorLabelFunc" as optional in docs description and code samples. --- pug/page-contents/media_content.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pug/page-contents/media_content.html b/pug/page-contents/media_content.html index 861b03ea7f..8c74c37354 100644 --- a/pug/page-contents/media_content.html +++ b/pug/page-contents/media_content.html @@ -281,6 +281,7 @@

        Initialization

        var elems = document.querySelectorAll('.slider'); var instances = M.Slider.init(elems, { // specify options here + /** Optional function which generates ARIA label for each indicator */ indicatorLabelFunc: (idx, current) => { let label = "Go to slide " + idx; if (current) { @@ -296,7 +297,8 @@

        Initialization

        $(document).ready(function() { $('.slider').slider({ // specify options here - indicatorLabelFunc: (idx, curr) => { + /** Optional function which generates ARIA label for each indicator */ + indicatorLabelFunc: (idx, current) => { let label = "Go to slide " + idx; if (current) { label = label + " (Current)"; @@ -362,7 +364,7 @@

        Options

        null

        - Function used to generate ARIA label to indicators (for accessibility purposes). + Optional function used to generate ARIA label to indicators (for accessibility purposes).

        It receives the current index, starting from "1", and a boolean indicating whether it is the current element or not.