소스 검색

Can select a document or a root element to listen to a keydown event. Also tab indices can be removed by the slideFocus option(#33).

NaotoshiFujita 5 년 전
부모
커밋
239eae5d03

+ 1 - 1
dist/css/splide-core.min.css

@@ -1 +1 @@
-@keyframes splide-loading{0%{transform:rotate(0)}to{transform:rotate(1turn)}}.splide__container{position:relative;box-sizing:border-box}.splide__list{margin:0!important;padding:0!important}.splide.is-active .splide__list{display:flex}.splide__pagination{display:inline-flex;align-items:center;width:95%;flex-wrap:wrap;justify-content:center;margin:0}.splide__pagination li{list-style-type:none;display:inline-block;line-height:1;margin:0}.splide{position:relative;visibility:hidden}.splide.is-active{visibility:visible}.splide__slide{position:relative;box-sizing:border-box;list-style-type:none!important;margin:0;outline:none;flex-shrink:0}.splide__slide img{vertical-align:bottom}.splide__slider{position:relative}.splide__spinner{position:absolute;top:0;left:0;right:0;bottom:0;margin:auto;display:inline-block;width:20px;height:20px;border-radius:50%;border:2px solid #999;border-left-color:transparent;animation:splide-loading 1s linear infinite}.splide__track{position:relative;z-index:0;overflow:hidden}.splide--draggable>.splide__track>.splide__list>.splide__slide{-webkit-user-select:none;user-select:none}.splide--fade>.splide__track>.splide__list{display:block}.splide--fade>.splide__track>.splide__list>.splide__slide{position:absolute;top:0;left:0;z-index:0;opacity:0}.splide--fade>.splide__track>.splide__list>.splide__slide.is-active{position:relative;z-index:1;opacity:1}.splide--rtl{direction:rtl}.splide--ttb>.splide__track>.splide__list{display:block}.splide--ttb>.splide__pagination{width:auto}
+@keyframes splide-loading{0%{transform:rotate(0)}to{transform:rotate(1turn)}}.splide__container{position:relative;box-sizing:border-box}.splide__list{margin:0!important;padding:0!important}.splide.is-active .splide__list{display:flex}.splide__pagination{display:inline-flex;align-items:center;width:95%;flex-wrap:wrap;justify-content:center;margin:0}.splide__pagination li{list-style-type:none;display:inline-block;line-height:1;margin:0}.splide{position:relative;visibility:hidden;outline:none}.splide.is-active{visibility:visible}.splide__slide{position:relative;box-sizing:border-box;list-style-type:none!important;margin:0;outline:none;flex-shrink:0}.splide__slide img{vertical-align:bottom}.splide__slider{position:relative}.splide__spinner{position:absolute;top:0;left:0;right:0;bottom:0;margin:auto;display:inline-block;width:20px;height:20px;border-radius:50%;border:2px solid #999;border-left-color:transparent;animation:splide-loading 1s linear infinite}.splide__track{position:relative;z-index:0;overflow:hidden}.splide--draggable>.splide__track>.splide__list>.splide__slide{-webkit-user-select:none;user-select:none}.splide--fade>.splide__track>.splide__list{display:block}.splide--fade>.splide__track>.splide__list>.splide__slide{position:absolute;top:0;left:0;z-index:0;opacity:0}.splide--fade>.splide__track>.splide__list>.splide__slide.is-active{position:relative;z-index:1;opacity:1}.splide--rtl{direction:rtl}.splide--ttb>.splide__track>.splide__list{display:block}.splide--ttb>.splide__pagination{width:auto}

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
dist/css/splide.min.css


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
dist/css/themes/splide-default.min.css


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
dist/css/themes/splide-sea-green.min.css


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
dist/css/themes/splide-skyblue.min.css


+ 92 - 63
dist/js/splide.esm.js

@@ -1,6 +1,6 @@
 /*!
  * Splide.js
- * Version  : 2.4.1
+ * Version  : 2.4.2
  * License  : MIT
  * Copyright: 2020 Naotoshi Fujita
  */
@@ -1292,11 +1292,14 @@ var DEFAULTS = {
   easing: 'cubic-bezier(.42,.65,.27,.99)',
 
   /**
-   * Whether to control a slide via keyboard.
+   * Whether to enable keyboard shortcuts
+   * - true or 'global': Listen to keydown event of the document.
+   * - 'focused': Listen to the keydown event of the slider root element. tabindex="0" will be added to the element.
+   * - false: Disable keyboard shortcuts.
    *
-   * @type {boolean}
+   * @type {boolean|string}
    */
-  keyboard: true,
+  keyboard: 'global',
 
   /**
    * Whether to allow mouse drag and touch swipe.
@@ -1370,6 +1373,13 @@ var DEFAULTS = {
    */
   accessibility: true,
 
+  /**
+   * Whether to add tabindex="0" to visible slides or not.
+   *
+   * @type {boolean}
+   */
+  slideFocus: true,
+
   /**
    * Determine if a slider is navigation for another.
    * Use "sync" API to synchronize two sliders.
@@ -4990,6 +5000,55 @@ var SRC_DATA_NAME = 'data-splide-lazy';
 
   return Lazyload;
 });
+// CONCATENATED MODULE: ./src/js/constants/a11y.js
+/**
+ * Export aria attribute names.
+ *
+ * @author    Naotoshi Fujita
+ * @copyright Naotoshi Fujita. All rights reserved.
+ */
+
+/**
+ * Attribute name for aria-current.
+ *
+ * @type {string}
+ */
+var ARIA_CURRENRT = 'aria-current';
+/**
+ * Attribute name for aria-control.
+ *
+ * @type {string}
+ */
+
+var ARIA_CONTROLS = 'aria-controls';
+/**
+ * Attribute name for aria-control.
+ *
+ * @type {string}
+ */
+
+var ARIA_LABEL = 'aria-label';
+/**
+ * Attribute name for aria-labelledby.
+ *
+ * @type {string}
+ */
+
+var ARIA_LABELLEDBY = 'aria-labelledby';
+/**
+ * Attribute name for aria-hidden.
+ *
+ * @type {string}
+ */
+
+var ARIA_HIDDEN = 'aria-hidden';
+/**
+ * Attribute name for tab-index.
+ *
+ * @type {string}
+ */
+
+var TAB_INDEX = 'tabindex';
 // CONCATENATED MODULE: ./src/js/components/keyboard/index.js
 /**
  * The component for controlling slides via keyboard.
@@ -4998,11 +5057,13 @@ var SRC_DATA_NAME = 'data-splide-lazy';
  * @copyright Naotoshi Fujita. All rights reserved.
  */
 
+
 /**
  * Map a key to a slide control.
  *
  * @type {Object}
  */
+
 var KEY_MAP = {
   ltr: {
     ArrowLeft: '<',
@@ -5034,82 +5095,47 @@ var KEY_MAP = {
  * @return {Object} - The component object.
  */
 
-/* harmony default export */ var keyboard = (function (Splide) {
+/* harmony default export */ var components_keyboard = (function (Splide) {
   /**
-   * Hold the root element.
+   * Hold the target element.
    *
-   * @type {Element}
+   * @type {Element|Document|undefined}
    */
-  var root = Splide.root;
+  var target;
   return {
     /**
      * Called when the component is mounted.
      */
     mount: function mount() {
-      var map = KEY_MAP[Splide.options.direction];
       Splide.on('mounted updated', function () {
-        Splide.off('keydown', root);
+        var options = Splide.options;
+        var root = Splide.root;
+        var map = KEY_MAP[options.direction];
+        var keyboard = options.keyboard;
+
+        if (target) {
+          Splide.off('keydown', target);
+          removeAttribute(root, TAB_INDEX);
+        }
+
+        if (keyboard) {
+          if (keyboard === 'focused') {
+            target = root;
+            setAttribute(root, TAB_INDEX, 0);
+          } else {
+            target = document;
+          }
 
-        if (Splide.options.keyboard) {
           Splide.on('keydown', function (e) {
             if (map[e.key]) {
               Splide.go(map[e.key]);
             }
-          }, root);
+          }, target);
         }
       });
     }
   };
 });
-// CONCATENATED MODULE: ./src/js/constants/a11y.js
-/**
- * Export aria attribute names.
- *
- * @author    Naotoshi Fujita
- * @copyright Naotoshi Fujita. All rights reserved.
- */
-
-/**
- * Attribute name for aria-current.
- *
- * @type {string}
- */
-var ARIA_CURRENRT = 'aria-current';
-/**
- * Attribute name for aria-control.
- *
- * @type {string}
- */
-
-var ARIA_CONTROLS = 'aria-controls';
-/**
- * Attribute name for aria-control.
- *
- * @type {string}
- */
-
-var ARIA_LABEL = 'aria-label';
-/**
- * Attribute name for aria-labelledby.
- *
- * @type {string}
- */
-
-var ARIA_LABELLEDBY = 'aria-labelledby';
-/**
- * Attribute name for aria-hidden.
- *
- * @type {string}
- */
-
-var ARIA_HIDDEN = 'aria-hidden';
-/**
- * Attribute name for tab-index.
- *
- * @type {string}
- */
-
-var TAB_INDEX = 'tabindex';
 // CONCATENATED MODULE: ./src/js/components/a11y/index.js
 /**
  * The component for enhancing accessibility.
@@ -5205,7 +5231,10 @@ var TAB_INDEX = 'tabindex';
 
   function updateSlide(slide, visible) {
     setAttribute(slide, ARIA_HIDDEN, !visible);
-    setAttribute(slide, TAB_INDEX, visible ? 0 : -1);
+
+    if (Splide.options.slideFocus) {
+      setAttribute(slide, TAB_INDEX, visible ? 0 : -1);
+    }
   }
   /**
    * Initialize arrows if they are available.
@@ -5703,7 +5732,7 @@ var COMPLETE = {
   Arrows: components_arrows,
   Pagination: components_pagination,
   LazyLoad: lazyload,
-  Keyboard: keyboard,
+  Keyboard: components_keyboard,
   Sync: sync,
   A11y: a11y
 };

+ 92 - 63
dist/js/splide.js

@@ -1,6 +1,6 @@
 /*!
  * Splide.js
- * Version  : 2.4.1
+ * Version  : 2.4.2
  * License  : MIT
  * Copyright: 2020 Naotoshi Fujita
  */
@@ -1282,11 +1282,14 @@ var DEFAULTS = {
   easing: 'cubic-bezier(.42,.65,.27,.99)',
 
   /**
-   * Whether to control a slide via keyboard.
+   * Whether to enable keyboard shortcuts
+   * - true or 'global': Listen to keydown event of the document.
+   * - 'focused': Listen to the keydown event of the slider root element. tabindex="0" will be added to the element.
+   * - false: Disable keyboard shortcuts.
    *
-   * @type {boolean}
+   * @type {boolean|string}
    */
-  keyboard: true,
+  keyboard: 'global',
 
   /**
    * Whether to allow mouse drag and touch swipe.
@@ -1360,6 +1363,13 @@ var DEFAULTS = {
    */
   accessibility: true,
 
+  /**
+   * Whether to add tabindex="0" to visible slides or not.
+   *
+   * @type {boolean}
+   */
+  slideFocus: true,
+
   /**
    * Determine if a slider is navigation for another.
    * Use "sync" API to synchronize two sliders.
@@ -4980,6 +4990,55 @@ var SRC_DATA_NAME = 'data-splide-lazy';
 
   return Lazyload;
 });
+// CONCATENATED MODULE: ./src/js/constants/a11y.js
+/**
+ * Export aria attribute names.
+ *
+ * @author    Naotoshi Fujita
+ * @copyright Naotoshi Fujita. All rights reserved.
+ */
+
+/**
+ * Attribute name for aria-current.
+ *
+ * @type {string}
+ */
+var ARIA_CURRENRT = 'aria-current';
+/**
+ * Attribute name for aria-control.
+ *
+ * @type {string}
+ */
+
+var ARIA_CONTROLS = 'aria-controls';
+/**
+ * Attribute name for aria-control.
+ *
+ * @type {string}
+ */
+
+var ARIA_LABEL = 'aria-label';
+/**
+ * Attribute name for aria-labelledby.
+ *
+ * @type {string}
+ */
+
+var ARIA_LABELLEDBY = 'aria-labelledby';
+/**
+ * Attribute name for aria-hidden.
+ *
+ * @type {string}
+ */
+
+var ARIA_HIDDEN = 'aria-hidden';
+/**
+ * Attribute name for tab-index.
+ *
+ * @type {string}
+ */
+
+var TAB_INDEX = 'tabindex';
 // CONCATENATED MODULE: ./src/js/components/keyboard/index.js
 /**
  * The component for controlling slides via keyboard.
@@ -4988,11 +5047,13 @@ var SRC_DATA_NAME = 'data-splide-lazy';
  * @copyright Naotoshi Fujita. All rights reserved.
  */
 
+
 /**
  * Map a key to a slide control.
  *
  * @type {Object}
  */
+
 var KEY_MAP = {
   ltr: {
     ArrowLeft: '<',
@@ -5024,82 +5085,47 @@ var KEY_MAP = {
  * @return {Object} - The component object.
  */
 
-/* harmony default export */ var keyboard = (function (Splide) {
+/* harmony default export */ var components_keyboard = (function (Splide) {
   /**
-   * Hold the root element.
+   * Hold the target element.
    *
-   * @type {Element}
+   * @type {Element|Document|undefined}
    */
-  var root = Splide.root;
+  var target;
   return {
     /**
      * Called when the component is mounted.
      */
     mount: function mount() {
-      var map = KEY_MAP[Splide.options.direction];
       Splide.on('mounted updated', function () {
-        Splide.off('keydown', root);
+        var options = Splide.options;
+        var root = Splide.root;
+        var map = KEY_MAP[options.direction];
+        var keyboard = options.keyboard;
+
+        if (target) {
+          Splide.off('keydown', target);
+          removeAttribute(root, TAB_INDEX);
+        }
+
+        if (keyboard) {
+          if (keyboard === 'focused') {
+            target = root;
+            setAttribute(root, TAB_INDEX, 0);
+          } else {
+            target = document;
+          }
 
-        if (Splide.options.keyboard) {
           Splide.on('keydown', function (e) {
             if (map[e.key]) {
               Splide.go(map[e.key]);
             }
-          }, root);
+          }, target);
         }
       });
     }
   };
 });
-// CONCATENATED MODULE: ./src/js/constants/a11y.js
-/**
- * Export aria attribute names.
- *
- * @author    Naotoshi Fujita
- * @copyright Naotoshi Fujita. All rights reserved.
- */
-
-/**
- * Attribute name for aria-current.
- *
- * @type {string}
- */
-var ARIA_CURRENRT = 'aria-current';
-/**
- * Attribute name for aria-control.
- *
- * @type {string}
- */
-
-var ARIA_CONTROLS = 'aria-controls';
-/**
- * Attribute name for aria-control.
- *
- * @type {string}
- */
-
-var ARIA_LABEL = 'aria-label';
-/**
- * Attribute name for aria-labelledby.
- *
- * @type {string}
- */
-
-var ARIA_LABELLEDBY = 'aria-labelledby';
-/**
- * Attribute name for aria-hidden.
- *
- * @type {string}
- */
-
-var ARIA_HIDDEN = 'aria-hidden';
-/**
- * Attribute name for tab-index.
- *
- * @type {string}
- */
-
-var TAB_INDEX = 'tabindex';
 // CONCATENATED MODULE: ./src/js/components/a11y/index.js
 /**
  * The component for enhancing accessibility.
@@ -5195,7 +5221,10 @@ var TAB_INDEX = 'tabindex';
 
   function updateSlide(slide, visible) {
     setAttribute(slide, ARIA_HIDDEN, !visible);
-    setAttribute(slide, TAB_INDEX, visible ? 0 : -1);
+
+    if (Splide.options.slideFocus) {
+      setAttribute(slide, TAB_INDEX, visible ? 0 : -1);
+    }
   }
   /**
    * Initialize arrows if they are available.
@@ -5693,7 +5722,7 @@ var COMPLETE = {
   Arrows: components_arrows,
   Pagination: components_pagination,
   LazyLoad: lazyload,
-  Keyboard: keyboard,
+  Keyboard: components_keyboard,
   Sync: sync,
   A11y: a11y
 };

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/js/splide.min.js


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


+ 1 - 1
package-lock.json

@@ -1,6 +1,6 @@
 {
   "name": "@splidejs/splide",
-  "version": "2.4.1",
+  "version": "2.4.2",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "@splidejs/splide",
-  "version": "2.4.1",
+  "version": "2.4.2",
   "description": "Splide is a lightweight and powerful slider without any dependencies.",
   "author": "Naotoshi Fujita",
   "license": "MIT",

+ 4 - 1
src/js/components/a11y/index.js

@@ -98,7 +98,10 @@ export default ( Splide, Components ) => {
 	 */
 	function updateSlide( slide, visible ) {
 		setAttribute( slide, ARIA_HIDDEN, ! visible );
-		setAttribute( slide, TAB_INDEX, visible ? 0 : -1 );
+
+		if ( Splide.options.slideFocus ) {
+			setAttribute( slide, TAB_INDEX, visible ? 0 : -1 );
+		}
 	}
 
 	/**

+ 24 - 8
src/js/components/keyboard/index.js

@@ -5,6 +5,9 @@
  * @copyright Naotoshi Fujita. All rights reserved.
  */
 
+import { removeAttribute, setAttribute } from "../../utils/dom";
+import { TAB_INDEX } from "../../constants/a11y";
+
 /**
  * Map a key to a slide control.
  *
@@ -44,28 +47,41 @@ const KEY_MAP = {
  */
 export default ( Splide ) => {
 	/**
-	 * Hold the root element.
+	 * Hold the target element.
 	 *
-	 * @type {Element}
+	 * @type {Element|Document|undefined}
 	 */
-	const root = Splide.root;
+	let target;
 
 	return {
 		/**
 		 * Called when the component is mounted.
 		 */
 		mount() {
-			const map = KEY_MAP[ Splide.options.direction ];
-
 			Splide.on( 'mounted updated', () => {
-				Splide.off( 'keydown', root );
+				const options  = Splide.options;
+				const root     = Splide.root;
+				const map      = KEY_MAP[ options.direction ];
+				const keyboard = options.keyboard;
+
+				if ( target ) {
+					Splide.off( 'keydown', target );
+					removeAttribute( root, TAB_INDEX );
+				}
+
+				if ( keyboard ) {
+					if ( keyboard === 'focused' ) {
+						target = root;
+						setAttribute( root, TAB_INDEX, 0 );
+					} else {
+						target = document;
+					}
 
-				if ( Splide.options.keyboard ) {
 					Splide.on( 'keydown', e => {
 						if ( map[ e.key ] ) {
 							Splide.go( map[ e.key ] );
 						}
-					}, root );
+					}, target );
 				}
 			} );
 		},

+ 13 - 3
src/js/constants/defaults.js

@@ -241,11 +241,14 @@ export const DEFAULTS = {
 	easing: 'cubic-bezier(.42,.65,.27,.99)',
 
 	/**
-	 * Whether to control a slide via keyboard.
+	 * Whether to enable keyboard shortcuts
+	 * - true or 'global': Listen to keydown event of the document.
+	 * - 'focused': Listen to the keydown event of the slider root element. tabindex="0" will be added to the element.
+	 * - false: Disable keyboard shortcuts.
 	 *
-	 * @type {boolean}
+	 * @type {boolean|string}
 	 */
-	keyboard: true,
+	keyboard: 'global',
 
 	/**
 	 * Whether to allow mouse drag and touch swipe.
@@ -319,6 +322,13 @@ export const DEFAULTS = {
 	 */
 	accessibility: true,
 
+	/**
+	 * Whether to add tabindex="0" to visible slides or not.
+	 *
+	 * @type {boolean}
+	 */
+	slideFocus: true,
+
 	/**
 	 * Determine if a slider is navigation for another.
 	 * Use "sync" API to synchronize two sliders.

+ 7 - 2
src/js/splide.d.ts

@@ -195,9 +195,9 @@ export interface SplideOptions extends BreakpointOptions {
 	preloadPages?: number;
 
 	/**
-	 * @default true
+	 * @default 'global'
 	 */
-	keyboard?: boolean;
+	keyboard?: 'global' | 'focused' | boolean;
 
 	/**
 	 * @default 30
@@ -239,6 +239,11 @@ export interface SplideOptions extends BreakpointOptions {
 	 */
 	accessibility?: boolean;
 
+	/**
+	 * @default true
+	 */
+	slideFocus?: true,
+
 	/**
 	 * @default false
 	 */

+ 1 - 0
src/sass/core/object/objects/_root.scss

@@ -1,6 +1,7 @@
 .splide {
   position: relative;
   visibility: hidden;
+  outline: none;
 
   &.is-active {
     visibility: visible;

+ 11 - 0
tests/functionality/a11y.test.js

@@ -57,4 +57,15 @@ describe( 'The A11y', () => {
 		} );
 	} );
 
+	test( 'should add tabindex to slides when slideFocus is true.', () => {
+		const splide = new Splide( '#splide', {}, COMPLETE );
+		splide.mount();
+		expect( splide.Components.Elements.slides[0].getAttribute( 'tabindex' ) ).toBe( '0' );
+	} );
+
+	test( 'should not add tabindex to slides when slideFocus is false.', () => {
+		const splide = new Splide( '#splide', { slideFocus: false }, COMPLETE );
+		splide.mount();
+		expect( splide.Components.Elements.slides[0].getAttribute( 'tabindex' ) ).toBeNull();
+	} );
 } );

+ 42 - 0
tests/functionality/keyboard.test.js

@@ -0,0 +1,42 @@
+import { minimum } from '../data/html';
+import Splide from '../../src/js/splide';
+import { COMPLETE } from '../../src/js/components';
+
+
+describe( 'The Keyboard', () => {
+	beforeEach( () => {
+		document.body.innerHTML = minimum;
+	} );
+
+	test( 'should listen to the keydown event of the document if the keyboard option is "global" or true.', done => {
+		const splide = new Splide( '#splide', {}, COMPLETE );
+		splide.mount();
+
+		splide.on( 'moved', () => {
+			expect( splide.index ).toBe( 1 );
+			done();
+		} );
+
+		document.dispatchEvent( new KeyboardEvent( 'keydown', { key: 'ArrowRight' } ) );
+		splide.Components.Elements.list.dispatchEvent( new Event( 'transitionend' ) );
+	} );
+
+	test( 'should add tabindex to the root element if the keyboard option is "focused".', () => {
+		const splide = new Splide( '#splide', { keyboard: 'focused' }, COMPLETE );
+		splide.mount();
+		expect( splide.root.getAttribute( 'tabindex' ) ).toBe( '0' );
+	} );
+
+	test( 'should listen to the keydown event of the root element if the keyboard option is "focused".', done => {
+		const splide = new Splide( '#splide', { keyboard: 'focused' }, COMPLETE );
+		splide.mount();
+
+		splide.on( 'moved', () => {
+			expect( splide.index ).toBe( 1 );
+			done();
+		} );
+
+		splide.root.dispatchEvent( new KeyboardEvent( 'keydown', { key: 'ArrowRight' } ) );
+		splide.Components.Elements.list.dispatchEvent( new Event( 'transitionend' ) );
+	} );
+} );

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.