Browse Source

Disable focus of major focusable elements in hidden slides.

NaotoshiFujita 3 years ago
parent
commit
82070f6da4

File diff suppressed because it is too large
+ 0 - 0
dist/js/splide-renderer.min.js


+ 7 - 0
dist/js/splide.cjs.js

@@ -861,9 +861,15 @@ function Slide$1(Splide2, index, slideIndex, slide) {
     }
     }
   }
   }
   function updateVisibility(visible) {
   function updateVisibility(visible) {
+    const { focusableNodes } = options;
     const ariaHidden = !visible && !isActive();
     const ariaHidden = !visible && !isActive();
     setAttribute(slide, ARIA_HIDDEN, ariaHidden || null);
     setAttribute(slide, ARIA_HIDDEN, ariaHidden || null);
     setAttribute(slide, TAB_INDEX, !ariaHidden && options.slideFocus ? 0 : null);
     setAttribute(slide, TAB_INDEX, !ariaHidden && options.slideFocus ? 0 : null);
+    if (focusableNodes) {
+      queryAll(slide, focusableNodes).forEach((node) => {
+        setAttribute(node, TAB_INDEX, ariaHidden ? -1 : null);
+      });
+    }
     if (visible !== hasClass(slide, CLASS_VISIBLE)) {
     if (visible !== hasClass(slide, CLASS_VISIBLE)) {
       toggleClass(slide, CLASS_VISIBLE, visible);
       toggleClass(slide, CLASS_VISIBLE, visible);
       emit(visible ? EVENT_VISIBLE : EVENT_HIDDEN, this);
       emit(visible ? EVENT_VISIBLE : EVENT_HIDDEN, this);
@@ -2199,6 +2205,7 @@ const DEFAULTS = {
   direction: "ltr",
   direction: "ltr",
   slideFocus: true,
   slideFocus: true,
   trimSpace: true,
   trimSpace: true,
+  focusableNodes: "a, button, textarea, input, select, iframe",
   classes: CLASSES,
   classes: CLASSES,
   i18n: I18N
   i18n: I18N
 };
 };

+ 7 - 0
dist/js/splide.esm.js

@@ -857,9 +857,15 @@ function Slide$1(Splide2, index, slideIndex, slide) {
     }
     }
   }
   }
   function updateVisibility(visible) {
   function updateVisibility(visible) {
+    const { focusableNodes } = options;
     const ariaHidden = !visible && !isActive();
     const ariaHidden = !visible && !isActive();
     setAttribute(slide, ARIA_HIDDEN, ariaHidden || null);
     setAttribute(slide, ARIA_HIDDEN, ariaHidden || null);
     setAttribute(slide, TAB_INDEX, !ariaHidden && options.slideFocus ? 0 : null);
     setAttribute(slide, TAB_INDEX, !ariaHidden && options.slideFocus ? 0 : null);
+    if (focusableNodes) {
+      queryAll(slide, focusableNodes).forEach((node) => {
+        setAttribute(node, TAB_INDEX, ariaHidden ? -1 : null);
+      });
+    }
     if (visible !== hasClass(slide, CLASS_VISIBLE)) {
     if (visible !== hasClass(slide, CLASS_VISIBLE)) {
       toggleClass(slide, CLASS_VISIBLE, visible);
       toggleClass(slide, CLASS_VISIBLE, visible);
       emit(visible ? EVENT_VISIBLE : EVENT_HIDDEN, this);
       emit(visible ? EVENT_VISIBLE : EVENT_HIDDEN, this);
@@ -2195,6 +2201,7 @@ const DEFAULTS = {
   direction: "ltr",
   direction: "ltr",
   slideFocus: true,
   slideFocus: true,
   trimSpace: true,
   trimSpace: true,
+  focusableNodes: "a, button, textarea, input, select, iframe",
   classes: CLASSES,
   classes: CLASSES,
   i18n: I18N
   i18n: I18N
 };
 };

+ 8 - 0
dist/js/splide.js

@@ -988,10 +988,17 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     }
     }
 
 
     function updateVisibility(visible) {
     function updateVisibility(visible) {
+      var focusableNodes = options.focusableNodes;
       var ariaHidden = !visible && !isActive();
       var ariaHidden = !visible && !isActive();
       setAttribute(slide, ARIA_HIDDEN, ariaHidden || null);
       setAttribute(slide, ARIA_HIDDEN, ariaHidden || null);
       setAttribute(slide, TAB_INDEX, !ariaHidden && options.slideFocus ? 0 : null);
       setAttribute(slide, TAB_INDEX, !ariaHidden && options.slideFocus ? 0 : null);
 
 
+      if (focusableNodes) {
+        queryAll(slide, focusableNodes).forEach(function (node) {
+          setAttribute(node, TAB_INDEX, ariaHidden ? -1 : null);
+        });
+      }
+
       if (visible !== hasClass(slide, CLASS_VISIBLE)) {
       if (visible !== hasClass(slide, CLASS_VISIBLE)) {
         toggleClass(slide, CLASS_VISIBLE, visible);
         toggleClass(slide, CLASS_VISIBLE, visible);
         emit(visible ? EVENT_VISIBLE : EVENT_HIDDEN, this);
         emit(visible ? EVENT_VISIBLE : EVENT_HIDDEN, this);
@@ -2690,6 +2697,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     direction: "ltr",
     direction: "ltr",
     slideFocus: true,
     slideFocus: true,
     trimSpace: true,
     trimSpace: true,
+    focusableNodes: "a, button, textarea, input, select, iframe",
     classes: CLASSES,
     classes: CLASSES,
     i18n: I18N
     i18n: I18N
   };
   };

File diff suppressed because it is too large
+ 0 - 0
dist/js/splide.js.map


File diff suppressed because it is too large
+ 0 - 0
dist/js/splide.min.js


BIN
dist/js/splide.min.js.gz


+ 1 - 1
dist/types/components/Slides/Slide.d.ts.map

@@ -1 +1 @@
-{"version":3,"file":"Slide.d.ts","sourceRoot":"","sources":["Slide.ts"],"names":[],"mappings":"AAiCA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAmB5C;;;;GAIG;AACH,MAAM,WAAY,cAAe,SAAQ,aAAa;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,WAAW,CAAC;IACnB,SAAS,EAAE,WAAW,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,YAAY,CAAC,EAAE,OAAO,GAAI,IAAI,CAAA;IAC1E,QAAQ,CAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAI,OAAO,CAAC;CACrD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAI,cAAc,CAuM7G"}
+{"version":3,"file":"Slide.d.ts","sourceRoot":"","sources":["Slide.ts"],"names":[],"mappings":"AAiCA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAmB5C;;;;GAIG;AACH,MAAM,WAAY,cAAe,SAAQ,aAAa;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,WAAW,CAAC;IACnB,SAAS,EAAE,WAAW,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,YAAY,CAAC,EAAE,OAAO,GAAI,IAAI,CAAA;IAC1E,QAAQ,CAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAI,OAAO,CAAC;CACrD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAI,cAAc,CA6M7G"}

+ 1 - 1
dist/types/constants/defaults.d.ts.map

@@ -1 +1 @@
-{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["defaults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAKnC;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,EAAE,OAkBtB,CAAC"}
+{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["defaults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAKnC;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,EAAE,OAmBtB,CAAC"}

+ 5 - 0
dist/types/types/options.d.ts

@@ -119,6 +119,11 @@ export interface Options extends ResponsiveOptions {
      * If `min`, the media query for breakpoints will be `min-width`, or otherwise, `max-width`.
      * If `min`, the media query for breakpoints will be `min-width`, or otherwise, `max-width`.
      */
      */
     mediaQuery?: 'min' | 'max';
     mediaQuery?: 'min' | 'max';
+    /**
+     * The selector to get focusable elements
+     * where `tabindex="-1"` will be assigned when their ascendant slide is hidden.
+     */
+    focusableNodes?: string;
     /**
     /**
      * Options for specific breakpoints.
      * Options for specific breakpoints.
      *
      *

File diff suppressed because it is too large
+ 0 - 0
dist/types/types/options.d.ts.map


+ 2 - 0
dist/types/utils/math/math/math.d.ts

@@ -0,0 +1,2 @@
+export declare const min: (...values: number[]) => number, max: (...values: number[]) => number, floor: (x: number) => number, ceil: (x: number) => number, abs: (x: number) => number;
+//# sourceMappingURL=../../../../../src/js/utils/math/math/math.d.ts.map

+ 1 - 0
dist/types/utils/math/math/math.d.ts.map

@@ -0,0 +1 @@
+{"version":3,"file":"math.d.ts","sourceRoot":"","sources":["math.ts"],"names":[],"mappings":"AAAA,eAAO,MAAQ,GAAG,mCAAE,GAAG,mCAAE,KAAK,yBAAE,IAAI,yBAAE,GAAG,uBAAS,CAAC"}

+ 9 - 3
src/js/components/Slides/Slide.ts

@@ -42,7 +42,7 @@ import {
   hasClass,
   hasClass,
   isHTMLButtonElement,
   isHTMLButtonElement,
   min,
   min,
-  pad,
+  pad, queryAll,
   rect,
   rect,
   removeAttribute,
   removeAttribute,
   removeClass,
   removeClass,
@@ -192,23 +192,29 @@ export function Slide( Splide: Splide, index: number, slideIndex: number, slide:
   }
   }
 
 
   /**
   /**
-   * Updates the status related with visibility.
+   * Updates classes and attributes related with visibility.
    *
    *
    * @param visible - Set `true` if the slide is visible.
    * @param visible - Set `true` if the slide is visible.
    */
    */
   function updateVisibility( this: SlideComponent, visible: boolean ): void {
   function updateVisibility( this: SlideComponent, visible: boolean ): void {
+    const { focusableNodes } = options;
     const ariaHidden = ! visible && ! isActive();
     const ariaHidden = ! visible && ! isActive();
 
 
     setAttribute( slide, ARIA_HIDDEN, ariaHidden || null );
     setAttribute( slide, ARIA_HIDDEN, ariaHidden || null );
     setAttribute( slide, TAB_INDEX, ! ariaHidden && options.slideFocus ? 0 : null );
     setAttribute( slide, TAB_INDEX, ! ariaHidden && options.slideFocus ? 0 : null );
 
 
+    if ( focusableNodes ) {
+      queryAll( slide, focusableNodes ).forEach( node => {
+        setAttribute( node, TAB_INDEX, ariaHidden ? -1 : null );
+      } );
+    }
+
     if ( visible !== hasClass( slide, CLASS_VISIBLE ) ) {
     if ( visible !== hasClass( slide, CLASS_VISIBLE ) ) {
       toggleClass( slide, CLASS_VISIBLE, visible );
       toggleClass( slide, CLASS_VISIBLE, visible );
       emit( visible ? EVENT_VISIBLE : EVENT_HIDDEN, this );
       emit( visible ? EVENT_VISIBLE : EVENT_HIDDEN, this );
     }
     }
   }
   }
 
 
-
   /**
   /**
    * Adds a CSS rule to the slider or the container.
    * Adds a CSS rule to the slider or the container.
    *
    *

+ 20 - 1
src/js/components/Slides/test/slide.test.ts

@@ -73,7 +73,7 @@ describe( 'Slide', () => {
     expect( callback ).toHaveBeenCalledWith( Slide2 );
     expect( callback ).toHaveBeenCalledWith( Slide2 );
   } );
   } );
 
 
-  test( 'can toggle the `is-visible` class and the aria-hidden attribute.', () => {
+  test( 'can toggle the `is-visible` class and the `aria-hidden` attribute.', () => {
     const splide = init( { speed: 0, perPage: 2 } );
     const splide = init( { speed: 0, perPage: 2 } );
     const { Slides } = splide.Components;
     const { Slides } = splide.Components;
 
 
@@ -123,6 +123,25 @@ describe( 'Slide', () => {
     expect( visibleSlides ).toEqual( [ Slide0, Slide1 ] );
     expect( visibleSlides ).toEqual( [ Slide0, Slide1 ] );
   } );
   } );
 
 
+  test( 'can disable focus of focusable descendants when the slide is hidden.', () => {
+    const splide = init( { speed: 0 } );
+    const { Slides } = splide.Components;
+    const Slide0 = Slides.getAt( 0 );
+    const a      = document.createElement( 'a' );
+    const button = document.createElement( 'button' );
+
+    Slide0.slide.appendChild( a );
+    Slide0.slide.appendChild( button );
+
+    splide.go( 1 );
+    expect( a.tabIndex ).toBe( -1 );
+    expect( button.tabIndex ).toBe( -1 );
+
+    splide.go( 0 );
+    expect( a.tabIndex ).toBe( 0 );
+    expect( button.tabIndex ).toBe( 0 );
+  } );
+
   test( 'can emit an event when the slide gets hidden.', () => {
   test( 'can emit an event when the slide gets hidden.', () => {
     const splide = init( { speed: 0, perPage: 2 } );
     const splide = init( { speed: 0, perPage: 2 } );
     const { Slides } = splide.Components;
     const { Slides } = splide.Components;

+ 1 - 0
src/js/constants/defaults.ts

@@ -25,6 +25,7 @@ export const DEFAULTS: Options = {
   direction        : 'ltr',
   direction        : 'ltr',
   slideFocus       : true,
   slideFocus       : true,
   trimSpace        : true,
   trimSpace        : true,
+  focusableNodes   : 'a, button, textarea, input, select, iframe',
   classes          : CLASSES,
   classes          : CLASSES,
   i18n             : I18N,
   i18n             : I18N,
 };
 };

+ 6 - 0
src/js/types/options.ts

@@ -141,6 +141,12 @@ export interface Options extends ResponsiveOptions {
    */
    */
   mediaQuery?: 'min' | 'max';
   mediaQuery?: 'min' | 'max';
 
 
+  /**
+   * The selector to get focusable elements
+   * where `tabindex="-1"` will be assigned when their ascendant slide is hidden.
+   */
+  focusableNodes?: string,
+
   /**
   /**
    * Options for specific breakpoints.
    * Options for specific breakpoints.
    *
    *

Some files were not shown because too many files changed in this diff