Przeglądaj źródła

Implement the live region so that SR can read contents of each slide when slides change.

NaotoshiFujita 3 lat temu
rodzic
commit
1f4b101a2b

Plik diff jest za duży
+ 0 - 0
dist/js/splide-renderer.min.js


+ 66 - 56
dist/js/splide.cjs.js

@@ -272,10 +272,12 @@ function assert(condition, message) {
   }
 }
 
-function nextTick(callback) {
-  setTimeout(callback);
+function apply(func) {
+  return func.bind(null, ...slice(arguments, 1));
 }
 
+const nextTick = setTimeout;
+
 const noop = () => {
 };
 
@@ -437,7 +439,7 @@ function EventInterface(Splide2) {
   function forEachEvent(targets, events, iteratee) {
     forEach(targets, (target) => {
       if (target) {
-        events.split(" ").forEach(iteratee.bind(null, target));
+        events.split(" ").forEach(apply(iteratee, target));
       }
     });
   }
@@ -701,6 +703,7 @@ const CLASSES = {
 function Elements(Splide2, Components2, options) {
   const { on } = EventInterface(Splide2);
   const { root } = Splide2;
+  const { i18n } = options;
   const elements = {};
   const slides = [];
   let classes;
@@ -713,7 +716,9 @@ function Elements(Splide2, Components2, options) {
     addClass(root, classes = getClasses());
   }
   function mount() {
-    on(EVENT_REFRESH, refresh, DEFAULT_EVENT_PRIORITY - 2);
+    const priority = DEFAULT_EVENT_PRIORITY - 2;
+    on(EVENT_REFRESH, destroy, priority);
+    on(EVENT_REFRESH, setup, priority);
     on(EVENT_UPDATED, update);
   }
   function destroy() {
@@ -721,10 +726,6 @@ function Elements(Splide2, Components2, options) {
     removeClass(root, classes);
     removeAttribute([root, track, list], ALL_ATTRIBUTES.concat("style"));
   }
-  function refresh() {
-    destroy();
-    setup();
-  }
   function update() {
     removeClass(root, classes);
     addClass(root, classes = getClasses());
@@ -757,7 +758,9 @@ function Elements(Splide2, Components2, options) {
     root.id = id;
     track.id = track.id || `${id}-track`;
     list.id = list.id || `${id}-list`;
-    setAttribute(root, ARIA_ROLEDESCRIPTION, "carousel");
+    setAttribute(root, ARIA_ROLEDESCRIPTION, i18n.carousel);
+    setAttribute(root, ROLE, root.tagName !== "SECTION" && options.role || null);
+    setAttribute(list, ROLE, "none");
   }
   function find(selector) {
     return child(root, selector) || child(slider, selector);
@@ -785,7 +788,7 @@ const FADE = "fade";
 function Slide$1(Splide2, index, slideIndex, slide) {
   const { on, emit, bind, destroy: destroyEvents } = EventInterface(Splide2);
   const { Components, root, options } = Splide2;
-  const { isNavigation, updateOnMove } = options;
+  const { isNavigation, updateOnMove, i18n } = options;
   const { resolve } = Components.Direction;
   const styles = getAttribute(slide, "style");
   const isClone = slideIndex > -1;
@@ -796,7 +799,8 @@ function Slide$1(Splide2, index, slideIndex, slide) {
     if (!isClone) {
       slide.id = `${root.id}-slide${pad(index + 1)}`;
       setAttribute(slide, ROLE, "group");
-      setAttribute(slide, ARIA_ROLEDESCRIPTION, "slide");
+      setAttribute(slide, ARIA_ROLEDESCRIPTION, i18n.slide);
+      setAttribute(slide, ARIA_LABEL, format(i18n.slideLabel, [index + 1, Splide2.length]));
     }
     listen();
   }
@@ -819,7 +823,7 @@ function Slide$1(Splide2, index, slideIndex, slide) {
   }
   function initNavigation() {
     const idx = isClone ? slideIndex : index;
-    const label = format(options.i18n.slideX, idx + 1);
+    const label = format(i18n.slideX, idx + 1);
     const controls = Splide2.splides.map((target) => target.splide.root.id).join(" ");
     setAttribute(slide, ARIA_LABEL, label);
     setAttribute(slide, ARIA_CONTROLS, controls);
@@ -904,7 +908,8 @@ function Slides(Splide2, Components2, options) {
   const Slides2 = [];
   function mount() {
     init();
-    on(EVENT_REFRESH, refresh);
+    on(EVENT_REFRESH, destroy);
+    on(EVENT_REFRESH, init);
     on([EVENT_MOUNTED, EVENT_REFRESH], () => {
       Slides2.sort((Slide1, Slide2) => Slide1.index - Slide2.index);
     });
@@ -920,10 +925,6 @@ function Slides(Splide2, Components2, options) {
     });
     empty(Slides2);
   }
-  function refresh() {
-    destroy();
-    init();
-  }
   function update() {
     forEach$1((Slide2) => {
       Slide2.update();
@@ -955,7 +956,7 @@ function Slides(Splide2, Components2, options) {
         const ref = slides[index];
         ref ? before(slide, ref) : append(list, slide);
         addClass(slide, options.classes.slide);
-        observeImages(slide, emit.bind(null, EVENT_RESIZE));
+        observeImages(slide, apply(emit, EVENT_RESIZE));
       }
     });
     emit(EVENT_REFRESH);
@@ -1121,7 +1122,8 @@ function Clones(Splide2, Components2, options) {
   let cloneCount;
   function mount() {
     init();
-    on(EVENT_REFRESH, refresh);
+    on(EVENT_REFRESH, destroy);
+    on(EVENT_REFRESH, init);
     on([EVENT_UPDATED, EVENT_RESIZE], observe);
   }
   function init() {
@@ -1134,10 +1136,6 @@ function Clones(Splide2, Components2, options) {
     remove(clones);
     empty(clones);
   }
-  function refresh() {
-    destroy();
-    init();
-  }
   function observe() {
     if (cloneCount < computeCloneCount()) {
       emit(EVENT_REFRESH);
@@ -1497,8 +1495,7 @@ function Arrows(Splide2, Components2, options) {
     }
     if (prev && next) {
       if (!arrows.prev) {
-        const { id } = Elements.track;
-        setAttribute([prev, next], ARIA_CONTROLS, id);
+        setAttribute([prev, next], ARIA_CONTROLS, Elements.list.id);
         arrows.prev = prev;
         arrows.next = next;
         listen();
@@ -1517,12 +1514,8 @@ function Arrows(Splide2, Components2, options) {
   function listen() {
     const { go } = Controller;
     on([EVENT_MOUNTED, EVENT_MOVED, EVENT_UPDATED, EVENT_REFRESH, EVENT_SCROLLED], update);
-    bind(next, "click", () => {
-      go(">", true);
-    });
-    bind(prev, "click", () => {
-      go("<", true);
-    });
+    bind(next, "click", apply(go, ">", true, void 0));
+    bind(prev, "click", apply(go, "<", true, void 0));
   }
   function createArrows() {
     wrapper = create("div", classes.arrows);
@@ -1647,30 +1640,25 @@ function Cover(Splide2, Components2, options) {
   const { on } = EventInterface(Splide2);
   function mount() {
     if (options.cover) {
-      on(EVENT_LAZYLOAD_LOADED, (img, Slide) => {
-        toggle(true, img, Slide);
-      });
-      on([EVENT_MOUNTED, EVENT_UPDATED, EVENT_REFRESH], apply.bind(null, true));
+      on(EVENT_LAZYLOAD_LOADED, apply(toggle, true));
+      on([EVENT_MOUNTED, EVENT_UPDATED, EVENT_REFRESH], apply(cover, true));
     }
   }
-  function destroy() {
-    apply(false);
-  }
-  function apply(cover) {
+  function cover(cover2) {
     Components2.Slides.forEach((Slide) => {
       const img = child(Slide.container || Slide.slide, "img");
       if (img && img.src) {
-        toggle(cover, img, Slide);
+        toggle(cover2, img, Slide);
       }
     });
   }
-  function toggle(cover, img, Slide) {
-    Slide.style("background", cover ? `center/cover no-repeat url("${img.src}")` : "", true);
-    display(img, cover ? "none" : "");
+  function toggle(cover2, img, Slide) {
+    Slide.style("background", cover2 ? `center/cover no-repeat url("${img.src}")` : "", true);
+    display(img, cover2 ? "none" : "");
   }
   return {
     mount,
-    destroy
+    destroy: apply(cover, false)
   };
 }
 
@@ -1860,9 +1848,7 @@ function Drag(Splide2, Components2, options) {
     if (isFree) {
       Controller.scroll(destination);
     } else if (Splide2.is(FADE)) {
-      const { length } = Splide2;
-      const index = Splide2.index + orient(sign(velocity));
-      Controller.go(rewind ? (index + length) % length : index);
+      Controller.go(orient(sign(velocity)) < 0 ? rewind ? "<" : "-" : rewind ? ">" : "+");
     } else if (Splide2.is(SLIDE) && exceeded && rewind) {
       Controller.go(exceededLimit(true) ? ">" : "<");
     } else {
@@ -1994,16 +1980,13 @@ function LazyLoad(Splide2, Components2, options) {
   function mount() {
     if (options.lazyLoad) {
       init();
-      on(EVENT_REFRESH, refresh);
+      on(EVENT_REFRESH, destroy);
+      on(EVENT_REFRESH, init);
       if (!isSequential) {
         on([EVENT_MOUNTED, EVENT_REFRESH, EVENT_MOVED, EVENT_SCROLLED], observe);
       }
     }
   }
-  function refresh() {
-    destroy();
-    init();
-  }
   function init() {
     Components2.Slides.forEach((_Slide) => {
       queryAll(_Slide.slide, IMAGE_SELECTOR).forEach((_img) => {
@@ -2114,10 +2097,9 @@ function Pagination(Splide2, Components2, options) {
     for (let i = 0; i < max; i++) {
       const li = create("li", null, list);
       const button = create("button", { class: classes.page, type: "button" }, li);
-      const controls = Slides.getIn(i).map((Slide) => Slide.slide.id);
       const text = !hasFocus() && perPage > 1 ? i18n.pageX : i18n.slideX;
-      bind(button, "click", onClick.bind(null, i));
-      setAttribute(button, ARIA_CONTROLS, controls.join(" "));
+      bind(button, "click", apply(onClick, i));
+      setAttribute(button, ARIA_CONTROLS, Components2.Elements.list.id);
       setAttribute(button, ARIA_LABEL, format(text, i + 1));
       items.push({ li, button, page: i });
     }
@@ -2215,6 +2197,29 @@ function Sync(Splide2, Components2, options) {
   };
 }
 
+function Live(Splide2, Components2, options) {
+  const { on } = EventInterface(Splide2);
+  const { list } = Components2.Elements;
+  const { live } = options;
+  function mount() {
+    if (live) {
+      setAttribute(list, ARIA_ATOMIC, false);
+      disable(!Components2.Autoplay.isPaused());
+      on(EVENT_AUTOPLAY_PLAY, apply(disable, true));
+      on(EVENT_AUTOPLAY_PAUSE, apply(disable, false));
+    }
+  }
+  function disable(disabled) {
+    if (live) {
+      setAttribute(list, ARIA_LIVE, disabled ? "off" : "polite");
+    }
+  }
+  return {
+    mount,
+    disable
+  };
+}
+
 function Wheel(Splide2, Components2, options) {
   const { bind } = EventInterface(Splide2);
   function mount() {
@@ -2259,6 +2264,7 @@ var ComponentConstructors = /*#__PURE__*/Object.freeze({
   LazyLoad: LazyLoad,
   Pagination: Pagination,
   Sync: Sync,
+  Live: Live,
   Wheel: Wheel
 });
 
@@ -2270,11 +2276,15 @@ const I18N = {
   slideX: "Go to slide %s",
   pageX: "Go to page %s",
   play: "Start autoplay",
-  pause: "Pause autoplay"
+  pause: "Pause autoplay",
+  carousel: "carousel",
+  slide: "slide",
+  slideLabel: "%s of %s"
 };
 
 const DEFAULTS = {
   type: "slide",
+  role: "region",
   speed: 400,
   waitForTransition: true,
   perPage: 1,

+ 66 - 56
dist/js/splide.esm.js

@@ -268,10 +268,12 @@ function assert(condition, message) {
   }
 }
 
-function nextTick(callback) {
-  setTimeout(callback);
+function apply(func) {
+  return func.bind(null, ...slice(arguments, 1));
 }
 
+const nextTick = setTimeout;
+
 const noop = () => {
 };
 
@@ -433,7 +435,7 @@ function EventInterface(Splide2) {
   function forEachEvent(targets, events, iteratee) {
     forEach(targets, (target) => {
       if (target) {
-        events.split(" ").forEach(iteratee.bind(null, target));
+        events.split(" ").forEach(apply(iteratee, target));
       }
     });
   }
@@ -697,6 +699,7 @@ const CLASSES = {
 function Elements(Splide2, Components2, options) {
   const { on } = EventInterface(Splide2);
   const { root } = Splide2;
+  const { i18n } = options;
   const elements = {};
   const slides = [];
   let classes;
@@ -709,7 +712,9 @@ function Elements(Splide2, Components2, options) {
     addClass(root, classes = getClasses());
   }
   function mount() {
-    on(EVENT_REFRESH, refresh, DEFAULT_EVENT_PRIORITY - 2);
+    const priority = DEFAULT_EVENT_PRIORITY - 2;
+    on(EVENT_REFRESH, destroy, priority);
+    on(EVENT_REFRESH, setup, priority);
     on(EVENT_UPDATED, update);
   }
   function destroy() {
@@ -717,10 +722,6 @@ function Elements(Splide2, Components2, options) {
     removeClass(root, classes);
     removeAttribute([root, track, list], ALL_ATTRIBUTES.concat("style"));
   }
-  function refresh() {
-    destroy();
-    setup();
-  }
   function update() {
     removeClass(root, classes);
     addClass(root, classes = getClasses());
@@ -753,7 +754,9 @@ function Elements(Splide2, Components2, options) {
     root.id = id;
     track.id = track.id || `${id}-track`;
     list.id = list.id || `${id}-list`;
-    setAttribute(root, ARIA_ROLEDESCRIPTION, "carousel");
+    setAttribute(root, ARIA_ROLEDESCRIPTION, i18n.carousel);
+    setAttribute(root, ROLE, root.tagName !== "SECTION" && options.role || null);
+    setAttribute(list, ROLE, "none");
   }
   function find(selector) {
     return child(root, selector) || child(slider, selector);
@@ -781,7 +784,7 @@ const FADE = "fade";
 function Slide$1(Splide2, index, slideIndex, slide) {
   const { on, emit, bind, destroy: destroyEvents } = EventInterface(Splide2);
   const { Components, root, options } = Splide2;
-  const { isNavigation, updateOnMove } = options;
+  const { isNavigation, updateOnMove, i18n } = options;
   const { resolve } = Components.Direction;
   const styles = getAttribute(slide, "style");
   const isClone = slideIndex > -1;
@@ -792,7 +795,8 @@ function Slide$1(Splide2, index, slideIndex, slide) {
     if (!isClone) {
       slide.id = `${root.id}-slide${pad(index + 1)}`;
       setAttribute(slide, ROLE, "group");
-      setAttribute(slide, ARIA_ROLEDESCRIPTION, "slide");
+      setAttribute(slide, ARIA_ROLEDESCRIPTION, i18n.slide);
+      setAttribute(slide, ARIA_LABEL, format(i18n.slideLabel, [index + 1, Splide2.length]));
     }
     listen();
   }
@@ -815,7 +819,7 @@ function Slide$1(Splide2, index, slideIndex, slide) {
   }
   function initNavigation() {
     const idx = isClone ? slideIndex : index;
-    const label = format(options.i18n.slideX, idx + 1);
+    const label = format(i18n.slideX, idx + 1);
     const controls = Splide2.splides.map((target) => target.splide.root.id).join(" ");
     setAttribute(slide, ARIA_LABEL, label);
     setAttribute(slide, ARIA_CONTROLS, controls);
@@ -900,7 +904,8 @@ function Slides(Splide2, Components2, options) {
   const Slides2 = [];
   function mount() {
     init();
-    on(EVENT_REFRESH, refresh);
+    on(EVENT_REFRESH, destroy);
+    on(EVENT_REFRESH, init);
     on([EVENT_MOUNTED, EVENT_REFRESH], () => {
       Slides2.sort((Slide1, Slide2) => Slide1.index - Slide2.index);
     });
@@ -916,10 +921,6 @@ function Slides(Splide2, Components2, options) {
     });
     empty(Slides2);
   }
-  function refresh() {
-    destroy();
-    init();
-  }
   function update() {
     forEach$1((Slide2) => {
       Slide2.update();
@@ -951,7 +952,7 @@ function Slides(Splide2, Components2, options) {
         const ref = slides[index];
         ref ? before(slide, ref) : append(list, slide);
         addClass(slide, options.classes.slide);
-        observeImages(slide, emit.bind(null, EVENT_RESIZE));
+        observeImages(slide, apply(emit, EVENT_RESIZE));
       }
     });
     emit(EVENT_REFRESH);
@@ -1117,7 +1118,8 @@ function Clones(Splide2, Components2, options) {
   let cloneCount;
   function mount() {
     init();
-    on(EVENT_REFRESH, refresh);
+    on(EVENT_REFRESH, destroy);
+    on(EVENT_REFRESH, init);
     on([EVENT_UPDATED, EVENT_RESIZE], observe);
   }
   function init() {
@@ -1130,10 +1132,6 @@ function Clones(Splide2, Components2, options) {
     remove(clones);
     empty(clones);
   }
-  function refresh() {
-    destroy();
-    init();
-  }
   function observe() {
     if (cloneCount < computeCloneCount()) {
       emit(EVENT_REFRESH);
@@ -1493,8 +1491,7 @@ function Arrows(Splide2, Components2, options) {
     }
     if (prev && next) {
       if (!arrows.prev) {
-        const { id } = Elements.track;
-        setAttribute([prev, next], ARIA_CONTROLS, id);
+        setAttribute([prev, next], ARIA_CONTROLS, Elements.list.id);
         arrows.prev = prev;
         arrows.next = next;
         listen();
@@ -1513,12 +1510,8 @@ function Arrows(Splide2, Components2, options) {
   function listen() {
     const { go } = Controller;
     on([EVENT_MOUNTED, EVENT_MOVED, EVENT_UPDATED, EVENT_REFRESH, EVENT_SCROLLED], update);
-    bind(next, "click", () => {
-      go(">", true);
-    });
-    bind(prev, "click", () => {
-      go("<", true);
-    });
+    bind(next, "click", apply(go, ">", true, void 0));
+    bind(prev, "click", apply(go, "<", true, void 0));
   }
   function createArrows() {
     wrapper = create("div", classes.arrows);
@@ -1643,30 +1636,25 @@ function Cover(Splide2, Components2, options) {
   const { on } = EventInterface(Splide2);
   function mount() {
     if (options.cover) {
-      on(EVENT_LAZYLOAD_LOADED, (img, Slide) => {
-        toggle(true, img, Slide);
-      });
-      on([EVENT_MOUNTED, EVENT_UPDATED, EVENT_REFRESH], apply.bind(null, true));
+      on(EVENT_LAZYLOAD_LOADED, apply(toggle, true));
+      on([EVENT_MOUNTED, EVENT_UPDATED, EVENT_REFRESH], apply(cover, true));
     }
   }
-  function destroy() {
-    apply(false);
-  }
-  function apply(cover) {
+  function cover(cover2) {
     Components2.Slides.forEach((Slide) => {
       const img = child(Slide.container || Slide.slide, "img");
       if (img && img.src) {
-        toggle(cover, img, Slide);
+        toggle(cover2, img, Slide);
       }
     });
   }
-  function toggle(cover, img, Slide) {
-    Slide.style("background", cover ? `center/cover no-repeat url("${img.src}")` : "", true);
-    display(img, cover ? "none" : "");
+  function toggle(cover2, img, Slide) {
+    Slide.style("background", cover2 ? `center/cover no-repeat url("${img.src}")` : "", true);
+    display(img, cover2 ? "none" : "");
   }
   return {
     mount,
-    destroy
+    destroy: apply(cover, false)
   };
 }
 
@@ -1856,9 +1844,7 @@ function Drag(Splide2, Components2, options) {
     if (isFree) {
       Controller.scroll(destination);
     } else if (Splide2.is(FADE)) {
-      const { length } = Splide2;
-      const index = Splide2.index + orient(sign(velocity));
-      Controller.go(rewind ? (index + length) % length : index);
+      Controller.go(orient(sign(velocity)) < 0 ? rewind ? "<" : "-" : rewind ? ">" : "+");
     } else if (Splide2.is(SLIDE) && exceeded && rewind) {
       Controller.go(exceededLimit(true) ? ">" : "<");
     } else {
@@ -1990,16 +1976,13 @@ function LazyLoad(Splide2, Components2, options) {
   function mount() {
     if (options.lazyLoad) {
       init();
-      on(EVENT_REFRESH, refresh);
+      on(EVENT_REFRESH, destroy);
+      on(EVENT_REFRESH, init);
       if (!isSequential) {
         on([EVENT_MOUNTED, EVENT_REFRESH, EVENT_MOVED, EVENT_SCROLLED], observe);
       }
     }
   }
-  function refresh() {
-    destroy();
-    init();
-  }
   function init() {
     Components2.Slides.forEach((_Slide) => {
       queryAll(_Slide.slide, IMAGE_SELECTOR).forEach((_img) => {
@@ -2110,10 +2093,9 @@ function Pagination(Splide2, Components2, options) {
     for (let i = 0; i < max; i++) {
       const li = create("li", null, list);
       const button = create("button", { class: classes.page, type: "button" }, li);
-      const controls = Slides.getIn(i).map((Slide) => Slide.slide.id);
       const text = !hasFocus() && perPage > 1 ? i18n.pageX : i18n.slideX;
-      bind(button, "click", onClick.bind(null, i));
-      setAttribute(button, ARIA_CONTROLS, controls.join(" "));
+      bind(button, "click", apply(onClick, i));
+      setAttribute(button, ARIA_CONTROLS, Components2.Elements.list.id);
       setAttribute(button, ARIA_LABEL, format(text, i + 1));
       items.push({ li, button, page: i });
     }
@@ -2211,6 +2193,29 @@ function Sync(Splide2, Components2, options) {
   };
 }
 
+function Live(Splide2, Components2, options) {
+  const { on } = EventInterface(Splide2);
+  const { list } = Components2.Elements;
+  const { live } = options;
+  function mount() {
+    if (live) {
+      setAttribute(list, ARIA_ATOMIC, false);
+      disable(!Components2.Autoplay.isPaused());
+      on(EVENT_AUTOPLAY_PLAY, apply(disable, true));
+      on(EVENT_AUTOPLAY_PAUSE, apply(disable, false));
+    }
+  }
+  function disable(disabled) {
+    if (live) {
+      setAttribute(list, ARIA_LIVE, disabled ? "off" : "polite");
+    }
+  }
+  return {
+    mount,
+    disable
+  };
+}
+
 function Wheel(Splide2, Components2, options) {
   const { bind } = EventInterface(Splide2);
   function mount() {
@@ -2255,6 +2260,7 @@ var ComponentConstructors = /*#__PURE__*/Object.freeze({
   LazyLoad: LazyLoad,
   Pagination: Pagination,
   Sync: Sync,
+  Live: Live,
   Wheel: Wheel
 });
 
@@ -2266,11 +2272,15 @@ const I18N = {
   slideX: "Go to slide %s",
   pageX: "Go to page %s",
   play: "Start autoplay",
-  pause: "Pause autoplay"
+  pause: "Pause autoplay",
+  carousel: "carousel",
+  slide: "slide",
+  slideLabel: "%s of %s"
 };
 
 const DEFAULTS = {
   type: "slide",
+  role: "region",
   speed: 400,
   waitForTransition: true,
   perPage: 1,

+ 77 - 66
dist/js/splide.js

@@ -298,10 +298,12 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     }
   }
 
-  function nextTick(callback) {
-    setTimeout(callback);
+  function apply(func) {
+    return func.bind.apply(func, [null].concat(slice(arguments, 1)));
   }
 
+  var nextTick = setTimeout;
+
   var noop = function noop() {};
 
   function raf(func) {
@@ -483,7 +485,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     function forEachEvent(targets, events, iteratee) {
       forEach(targets, function (target) {
         if (target) {
-          events.split(" ").forEach(iteratee.bind(null, target));
+          events.split(" ").forEach(apply(iteratee, target));
         }
       });
     }
@@ -779,6 +781,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
         on = _EventInterface.on;
 
     var root = Splide2.root;
+    var i18n = options.i18n;
     var elements = {};
     var slides = [];
     var classes;
@@ -793,7 +796,9 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     }
 
     function mount() {
-      on(EVENT_REFRESH, refresh, DEFAULT_EVENT_PRIORITY - 2);
+      var priority = DEFAULT_EVENT_PRIORITY - 2;
+      on(EVENT_REFRESH, destroy, priority);
+      on(EVENT_REFRESH, setup, priority);
       on(EVENT_UPDATED, update);
     }
 
@@ -803,11 +808,6 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
       removeAttribute([root, track, list], ALL_ATTRIBUTES.concat("style"));
     }
 
-    function refresh() {
-      destroy();
-      setup();
-    }
-
     function update() {
       removeClass(root, classes);
       addClass(root, classes = getClasses());
@@ -842,7 +842,9 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
       root.id = id;
       track.id = track.id || id + "-track";
       list.id = list.id || id + "-list";
-      setAttribute(root, ARIA_ROLEDESCRIPTION, "carousel");
+      setAttribute(root, ARIA_ROLEDESCRIPTION, i18n.carousel);
+      setAttribute(root, ROLE, root.tagName !== "SECTION" && options.role || null);
+      setAttribute(list, ROLE, "none");
     }
 
     function find(selector) {
@@ -875,7 +877,8 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
         root = Splide2.root,
         options = Splide2.options;
     var isNavigation = options.isNavigation,
-        updateOnMove = options.updateOnMove;
+        updateOnMove = options.updateOnMove,
+        i18n = options.i18n;
     var resolve = Components.Direction.resolve;
     var styles = getAttribute(slide, "style");
     var isClone = slideIndex > -1;
@@ -887,7 +890,8 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
       if (!isClone) {
         slide.id = root.id + "-slide" + pad(index + 1);
         setAttribute(slide, ROLE, "group");
-        setAttribute(slide, ARIA_ROLEDESCRIPTION, "slide");
+        setAttribute(slide, ARIA_ROLEDESCRIPTION, i18n.slide);
+        setAttribute(slide, ARIA_LABEL, format(i18n.slideLabel, [index + 1, Splide2.length]));
       }
 
       listen();
@@ -915,7 +919,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
 
     function initNavigation() {
       var idx = isClone ? slideIndex : index;
-      var label = format(options.i18n.slideX, idx + 1);
+      var label = format(i18n.slideX, idx + 1);
       var controls = Splide2.splides.map(function (target) {
         return target.splide.root.id;
       }).join(" ");
@@ -1024,7 +1028,8 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
 
     function mount() {
       init();
-      on(EVENT_REFRESH, refresh);
+      on(EVENT_REFRESH, destroy);
+      on(EVENT_REFRESH, init);
       on([EVENT_MOUNTED, EVENT_REFRESH], function () {
         Slides2.sort(function (Slide1, Slide2) {
           return Slide1.index - Slide2.index;
@@ -1045,11 +1050,6 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
       empty(Slides2);
     }
 
-    function refresh() {
-      destroy();
-      init();
-    }
-
     function update() {
       forEach$1(function (Slide2) {
         Slide2.update();
@@ -1091,7 +1091,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
           var ref = slides[index];
           ref ? before(slide, ref) : append(list, slide);
           addClass(slide, options.classes.slide);
-          observeImages(slide, emit.bind(null, EVENT_RESIZE));
+          observeImages(slide, apply(emit, EVENT_RESIZE));
         }
       });
       emit(EVENT_REFRESH);
@@ -1304,7 +1304,8 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
 
     function mount() {
       init();
-      on(EVENT_REFRESH, refresh);
+      on(EVENT_REFRESH, destroy);
+      on(EVENT_REFRESH, init);
       on([EVENT_UPDATED, EVENT_RESIZE], observe);
     }
 
@@ -1320,11 +1321,6 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
       empty(clones);
     }
 
-    function refresh() {
-      destroy();
-      init();
-    }
-
     function observe() {
       if (cloneCount < computeCloneCount()) {
         emit(EVENT_REFRESH);
@@ -1778,8 +1774,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
 
       if (prev && next) {
         if (!arrows.prev) {
-          var id = Elements.track.id;
-          setAttribute([prev, next], ARIA_CONTROLS, id);
+          setAttribute([prev, next], ARIA_CONTROLS, Elements.list.id);
           arrows.prev = prev;
           arrows.next = next;
           listen();
@@ -1801,12 +1796,8 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     function listen() {
       var go = Controller.go;
       on([EVENT_MOUNTED, EVENT_MOVED, EVENT_UPDATED, EVENT_REFRESH, EVENT_SCROLLED], update);
-      bind(next, "click", function () {
-        go(">", true);
-      });
-      bind(prev, "click", function () {
-        go("<", true);
-      });
+      bind(next, "click", apply(go, ">", true, void 0));
+      bind(prev, "click", apply(go, "<", true, void 0));
     }
 
     function createArrows() {
@@ -1961,35 +1952,29 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
 
     function mount() {
       if (options.cover) {
-        on(EVENT_LAZYLOAD_LOADED, function (img, Slide) {
-          toggle(true, img, Slide);
-        });
-        on([EVENT_MOUNTED, EVENT_UPDATED, EVENT_REFRESH], apply.bind(null, true));
+        on(EVENT_LAZYLOAD_LOADED, apply(toggle, true));
+        on([EVENT_MOUNTED, EVENT_UPDATED, EVENT_REFRESH], apply(cover, true));
       }
     }
 
-    function destroy() {
-      apply(false);
-    }
-
-    function apply(cover) {
+    function cover(cover2) {
       Components2.Slides.forEach(function (Slide) {
         var img = child(Slide.container || Slide.slide, "img");
 
         if (img && img.src) {
-          toggle(cover, img, Slide);
+          toggle(cover2, img, Slide);
         }
       });
     }
 
-    function toggle(cover, img, Slide) {
-      Slide.style("background", cover ? "center/cover no-repeat url(\"" + img.src + "\")" : "", true);
-      display(img, cover ? "none" : "");
+    function toggle(cover2, img, Slide) {
+      Slide.style("background", cover2 ? "center/cover no-repeat url(\"" + img.src + "\")" : "", true);
+      display(img, cover2 ? "none" : "");
     }
 
     return {
       mount: mount,
-      destroy: destroy
+      destroy: apply(cover, false)
     };
   }
 
@@ -2396,7 +2381,8 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     function mount() {
       if (options.lazyLoad) {
         init();
-        on(EVENT_REFRESH, refresh);
+        on(EVENT_REFRESH, destroy);
+        on(EVENT_REFRESH, init);
 
         if (!isSequential) {
           on([EVENT_MOUNTED, EVENT_REFRESH, EVENT_MOVED, EVENT_SCROLLED], observe);
@@ -2404,11 +2390,6 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
       }
     }
 
-    function refresh() {
-      destroy();
-      init();
-    }
-
     function init() {
       Components2.Slides.forEach(function (_Slide) {
         queryAll(_Slide.slide, IMAGE_SELECTOR).forEach(function (_img) {
@@ -2562,12 +2543,9 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
           class: classes.page,
           type: "button"
         }, li);
-        var controls = Slides.getIn(i).map(function (Slide) {
-          return Slide.slide.id;
-        });
         var text = !hasFocus() && perPage > 1 ? i18n.pageX : i18n.slideX;
-        bind(button, "click", onClick.bind(null, i));
-        setAttribute(button, ARIA_CONTROLS, controls.join(" "));
+        bind(button, "click", apply(onClick, i));
+        setAttribute(button, ARIA_CONTROLS, Components2.Elements.list.id);
         setAttribute(button, ARIA_LABEL, format(text, i + 1));
         items.push({
           li: li,
@@ -2690,9 +2668,37 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     };
   }
 
-  function Wheel(Splide2, Components2, options) {
+  function Live(Splide2, Components2, options) {
     var _EventInterface16 = EventInterface(Splide2),
-        bind = _EventInterface16.bind;
+        on = _EventInterface16.on;
+
+    var list = Components2.Elements.list;
+    var live = options.live;
+
+    function mount() {
+      if (live) {
+        setAttribute(list, ARIA_ATOMIC, false);
+        disable(!Components2.Autoplay.isPaused());
+        on(EVENT_AUTOPLAY_PLAY, apply(disable, true));
+        on(EVENT_AUTOPLAY_PAUSE, apply(disable, false));
+      }
+    }
+
+    function disable(disabled) {
+      if (live) {
+        setAttribute(list, ARIA_LIVE, disabled ? "off" : "polite");
+      }
+    }
+
+    return {
+      mount: mount,
+      disable: disable
+    };
+  }
+
+  function Wheel(Splide2, Components2, options) {
+    var _EventInterface17 = EventInterface(Splide2),
+        bind = _EventInterface17.bind;
 
     function mount() {
       if (options.wheel) {
@@ -2740,6 +2746,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     LazyLoad: LazyLoad,
     Pagination: Pagination,
     Sync: Sync,
+    Live: Live,
     Wheel: Wheel
   });
   var I18N = {
@@ -2750,10 +2757,14 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     slideX: "Go to slide %s",
     pageX: "Go to page %s",
     play: "Start autoplay",
-    pause: "Pause autoplay"
+    pause: "Pause autoplay",
+    carousel: "carousel",
+    slide: "slide",
+    slideLabel: "%s of %s"
   };
   var DEFAULTS = {
     type: "slide",
+    role: "region",
     speed: 400,
     waitForTransition: true,
     perPage: 1,
@@ -2776,8 +2787,8 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
   };
 
   function Fade(Splide2, Components2, options) {
-    var _EventInterface17 = EventInterface(Splide2),
-        on = _EventInterface17.on;
+    var _EventInterface18 = EventInterface(Splide2),
+        on = _EventInterface18.on;
 
     function mount() {
       on([EVENT_MOUNTED, EVENT_REFRESH], function () {
@@ -2804,8 +2815,8 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
   }
 
   function Slide(Splide2, Components2, options) {
-    var _EventInterface18 = EventInterface(Splide2),
-        bind = _EventInterface18.bind;
+    var _EventInterface19 = EventInterface(Splide2),
+        bind = _EventInterface19.bind;
 
     var Move = Components2.Move,
         Controller = Components2.Controller;

Plik diff jest za duży
+ 0 - 0
dist/js/splide.min.js


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


Plik diff jest za duży
+ 0 - 0
dist/js/splide.min.js.map


+ 69 - 2
dist/types/index.d.ts

@@ -150,6 +150,15 @@ interface SlideComponent extends BaseComponent {
 interface LazyLoadComponent extends BaseComponent {
 }
 
+/**
+ * The interface for the Live component.
+ *
+ * @since 3.7.0
+ */
+interface LiveComponent extends BaseComponent {
+    disable(disabled: boolean): void;
+}
+
 /**
  * The interface for the Move component.
  *
@@ -234,6 +243,25 @@ interface SyncComponent extends BaseComponent {
 interface WheelComponent extends BaseComponent {
 }
 
+/**
+ * The collection of i18n strings.
+ *
+ * @since 3.0.0
+ */
+declare const I18N: {
+    prev: string;
+    next: string;
+    first: string;
+    last: string;
+    slideX: string;
+    pageX: string;
+    play: string;
+    pause: string;
+    carousel: string;
+    slide: string;
+    slideLabel: string;
+};
+
 /**
  * The interface for options.
  *
@@ -247,6 +275,11 @@ interface Options extends ResponsiveOptions {
      * - 'fade' : A slider with the fade transition. This does not support the perPage option.
      */
     type?: string;
+    /**
+     * The `role` attribute for the root element.
+     * If the tag is `<section>`, this value will not be used. The default value is `'region'`.
+     */
+    role?: string;
     /**
      * Determines whether to disable any actions while the slider is transitioning.
      * Even if `false`, the slider forcibly waits for transition on the loop points.
@@ -368,6 +401,11 @@ interface Options extends ResponsiveOptions {
      * The selector for nodes that cannot be dragged.
      */
     noDrag?: string;
+    /**
+     * Enables the live region by `aria-live`.
+     * If `true`, screen readers will read a content of each slide whenever slide changes.
+     */
+    live?: boolean;
     /**
      * Determines whether to use the Transition component or not.
      */
@@ -397,7 +435,7 @@ interface Options extends ResponsiveOptions {
     /**
      * The collection of i18n strings.
      */
-    i18n?: Record<string, string>;
+    i18n?: Record<keyof typeof I18N | string, string>;
 }
 /**
  * The interface for options that can correspond with breakpoints.
@@ -613,6 +651,7 @@ interface Components {
     Pagination: PaginationComponent;
     Sync: SyncComponent;
     Wheel: WheelComponent;
+    Live: LiveComponent;
     Transition: TransitionComponent;
 }
 
@@ -654,6 +693,34 @@ interface EventMap {
     'lazyload:loaded': (img: HTMLImageElement, Slide: SlideComponent) => void;
 }
 
+/**
+ * Casts T to U.
+ *
+ * @internal
+ */
+declare type Cast<T, U> = T extends U ? T : U;
+/**
+ * Pushes U to tuple T.
+ *
+ * @internal
+ */
+declare type Push<T extends any[], U = any> = [...T, U];
+/**
+ * Removes the first type from the tuple T.
+ *
+ * @internal
+ */
+declare type Shift<T extends any[]> = ((...args: T) => any) extends (arg: any, ...args: infer A) => any ? A : never;
+/**
+ * Removes the N types from the tuple T.
+ *
+ * @internal
+ */
+declare type ShiftN<T extends any[], N extends number, C extends any[] = []> = {
+    0: T;
+    1: ShiftN<Shift<T>, N, Push<C>>;
+}[C['length'] extends N ? 0 : 1] extends infer A ? Cast<A, any[]> : never;
+
 /**
  * The interface for the Slides component.
  *
@@ -1521,4 +1588,4 @@ declare const CLASSES: {
     spinner: string;
 };
 
-export { AnyFunction, ArrowsComponent, AutoplayComponent, BaseComponent, CLASSES, CLASS_ACTIVE, CLASS_ARROW, CLASS_ARROWS, CLASS_ARROW_NEXT, CLASS_ARROW_PREV, CLASS_AUTOPLAY, CLASS_CLONE, CLASS_CONTAINER, CLASS_INITIALIZED, CLASS_LIST, CLASS_LOADING, CLASS_NEXT, CLASS_PAGINATION, CLASS_PAGINATION_PAGE, CLASS_PAUSE, CLASS_PLAY, CLASS_PREV, CLASS_PROGRESS, CLASS_PROGRESS_BAR, CLASS_ROOT, CLASS_SLIDE, CLASS_SLIDER, CLASS_SPINNER, CLASS_SR, CLASS_TRACK, CLASS_VISIBLE, ClonesComponent, ComponentConstructor, Components, ControllerComponent, CoverComponent, DirectionComponent, DragComponent, EVENT_ACTIVE, EVENT_ARROWS_MOUNTED, EVENT_ARROWS_UPDATED, EVENT_AUTOPLAY_PAUSE, EVENT_AUTOPLAY_PLAY, EVENT_AUTOPLAY_PLAYING, EVENT_CLICK, EVENT_DESTROY, EVENT_DRAG, EVENT_DRAGGED, EVENT_DRAGGING, EVENT_HIDDEN, EVENT_INACTIVE, EVENT_LAZYLOAD_LOADED, EVENT_MOUNTED, EVENT_MOVE, EVENT_MOVED, EVENT_NAVIGATION_MOUNTED, EVENT_PAGINATION_MOUNTED, EVENT_PAGINATION_UPDATED, EVENT_READY, EVENT_REFRESH, EVENT_REPOSITIONED, EVENT_RESIZE, EVENT_RESIZED, EVENT_SCROLL, EVENT_SCROLLED, EVENT_SHIFTED, EVENT_SLIDE_KEYDOWN, EVENT_UPDATED, EVENT_VISIBLE, ElementsComponent, EventBus, EventBusCallback, EventBusObject, EventHandler, EventInterface, EventInterfaceObject, EventMap, KeyboardComponent, LayoutComponent, LazyLoadComponent, MoveComponent, Options, OptionsComponent, PaginationComponent, PaginationData, PaginationItem, RequestInterval, RequestIntervalInterface, ResponsiveOptions, STATUS_CLASSES, ScrollComponent, SlideComponent, SlidesComponent, Splide, SplideRenderer, State, StateObject, SyncComponent, SyncTarget, Throttle, ThrottleInstance, TransitionComponent, WheelComponent, Splide as default };
+export { AnyFunction, ArrowsComponent, AutoplayComponent, BaseComponent, CLASSES, CLASS_ACTIVE, CLASS_ARROW, CLASS_ARROWS, CLASS_ARROW_NEXT, CLASS_ARROW_PREV, CLASS_AUTOPLAY, CLASS_CLONE, CLASS_CONTAINER, CLASS_INITIALIZED, CLASS_LIST, CLASS_LOADING, CLASS_NEXT, CLASS_PAGINATION, CLASS_PAGINATION_PAGE, CLASS_PAUSE, CLASS_PLAY, CLASS_PREV, CLASS_PROGRESS, CLASS_PROGRESS_BAR, CLASS_ROOT, CLASS_SLIDE, CLASS_SLIDER, CLASS_SPINNER, CLASS_SR, CLASS_TRACK, CLASS_VISIBLE, Cast, ClonesComponent, ComponentConstructor, Components, ControllerComponent, CoverComponent, DirectionComponent, DragComponent, EVENT_ACTIVE, EVENT_ARROWS_MOUNTED, EVENT_ARROWS_UPDATED, EVENT_AUTOPLAY_PAUSE, EVENT_AUTOPLAY_PLAY, EVENT_AUTOPLAY_PLAYING, EVENT_CLICK, EVENT_DESTROY, EVENT_DRAG, EVENT_DRAGGED, EVENT_DRAGGING, EVENT_HIDDEN, EVENT_INACTIVE, EVENT_LAZYLOAD_LOADED, EVENT_MOUNTED, EVENT_MOVE, EVENT_MOVED, EVENT_NAVIGATION_MOUNTED, EVENT_PAGINATION_MOUNTED, EVENT_PAGINATION_UPDATED, EVENT_READY, EVENT_REFRESH, EVENT_REPOSITIONED, EVENT_RESIZE, EVENT_RESIZED, EVENT_SCROLL, EVENT_SCROLLED, EVENT_SHIFTED, EVENT_SLIDE_KEYDOWN, EVENT_UPDATED, EVENT_VISIBLE, ElementsComponent, EventBus, EventBusCallback, EventBusObject, EventHandler, EventInterface, EventInterfaceObject, EventMap, KeyboardComponent, LayoutComponent, LazyLoadComponent, LiveComponent, MoveComponent, Options, OptionsComponent, PaginationComponent, PaginationData, PaginationItem, Push, RequestInterval, RequestIntervalInterface, ResponsiveOptions, STATUS_CLASSES, ScrollComponent, Shift, ShiftN, SlideComponent, SlidesComponent, Splide, SplideRenderer, State, StateObject, SyncComponent, SyncTarget, Throttle, ThrottleInstance, TransitionComponent, WheelComponent, Splide as default };

+ 15 - 6
src/js/components/Arrows/Arrows.ts

@@ -11,7 +11,18 @@ import {
 import { EventInterface } from '../../constructors';
 import { Splide } from '../../core/Splide/Splide';
 import { BaseComponent, Components, Options } from '../../types';
-import { append, before, child, create, display, parseHtml, remove, removeAttribute, setAttribute } from '../../utils';
+import {
+  append,
+  apply,
+  before,
+  child,
+  create,
+  display,
+  parseHtml,
+  remove,
+  removeAttribute,
+  setAttribute,
+} from '../../utils';
 import { PATH, SIZE, XML_NAME_SPACE } from './path';
 
 
@@ -85,9 +96,7 @@ export function Arrows( Splide: Splide, Components: Components, options: Options
 
     if ( prev && next ) {
       if ( ! arrows.prev ) {
-        const { id } = Elements.track;
-
-        setAttribute( [ prev, next ], ARIA_CONTROLS, id );
+        setAttribute( [ prev, next ], ARIA_CONTROLS, Elements.list.id );
 
         arrows.prev = prev;
         arrows.next = next;
@@ -118,8 +127,8 @@ export function Arrows( Splide: Splide, Components: Components, options: Options
   function listen(): void {
     const { go } = Controller;
     on( [ EVENT_MOUNTED, EVENT_MOVED, EVENT_UPDATED, EVENT_REFRESH, EVENT_SCROLLED ], update );
-    bind( next, 'click', () => { go( '>', true ) } );
-    bind( prev, 'click', () => { go( '<', true ) } );
+    bind( next, 'click', apply( go, '>', true, undefined ) );
+    bind( prev, 'click', apply( go, '<', true, undefined ) );
   }
 
   /**

+ 2 - 10
src/js/components/Clones/Clones.ts

@@ -45,7 +45,8 @@ export function Clones( Splide: Splide, Components: Components, options: Options
    */
   function mount(): void {
     init();
-    on( EVENT_REFRESH, refresh );
+    on( EVENT_REFRESH, destroy );
+    on( EVENT_REFRESH, init );
     on( [ EVENT_UPDATED, EVENT_RESIZE ], observe );
   }
 
@@ -67,15 +68,6 @@ export function Clones( Splide: Splide, Components: Components, options: Options
     empty( clones );
   }
 
-  /**
-   * Discards all clones and regenerates them.
-   * Must do this before the Elements component collects slide elements.
-   */
-  function refresh(): void {
-    destroy();
-    init();
-  }
-
   /**
    * Observes the required clone count and refreshes the slider if necessary.
    */

+ 5 - 12
src/js/components/Cover/Cover.ts

@@ -2,7 +2,7 @@ import { EVENT_LAZYLOAD_LOADED, EVENT_MOUNTED, EVENT_REFRESH, EVENT_UPDATED } fr
 import { EventInterface } from '../../constructors';
 import { Splide } from '../../core/Splide/Splide';
 import { BaseComponent, Components, Options } from '../../types';
-import { child, display } from '../../utils';
+import { apply, child, display } from '../../utils';
 import { SlideComponent } from '../Slides/Slide';
 
 
@@ -33,24 +33,17 @@ export function Cover( Splide: Splide, Components: Components, options: Options
    */
   function mount(): void {
     if ( options.cover ) {
-      on( EVENT_LAZYLOAD_LOADED, ( img, Slide ) => { toggle( true, img, Slide ) } );
-      on( [ EVENT_MOUNTED, EVENT_UPDATED, EVENT_REFRESH ], apply.bind( null, true ) );
+      on( EVENT_LAZYLOAD_LOADED, apply( toggle, true ) );
+      on( [ EVENT_MOUNTED, EVENT_UPDATED, EVENT_REFRESH ], apply( cover, true ) );
     }
   }
 
-  /**
-   * Destroys the component.
-   */
-  function destroy(): void {
-    apply( false );
-  }
-
   /**
    * Sets/removes the background image to/from all slides.
    *
    * @param cover - If `false`, removes the background image.
    */
-  function apply( cover: boolean ): void {
+  function cover( cover: boolean ): void {
     Components.Slides.forEach( Slide => {
       const img = child<HTMLImageElement>( Slide.container || Slide.slide, 'img' );
 
@@ -74,6 +67,6 @@ export function Cover( Splide: Splide, Components: Components, options: Options
 
   return {
     mount,
-    destroy,
+    destroy: apply( cover, false ),
   };
 }

+ 8 - 11
src/js/components/Elements/Elements.ts

@@ -1,4 +1,4 @@
-import { ALL_ATTRIBUTES, ARIA_ROLEDESCRIPTION } from '../../constants/attributes';
+import { ALL_ATTRIBUTES, ARIA_ROLEDESCRIPTION, ROLE } from '../../constants/attributes';
 import {
   CLASS_ACTIVE,
   CLASS_ARROW_NEXT,
@@ -80,6 +80,7 @@ export interface ElementsComponent extends BaseComponent, ElementCollection {
 export function Elements( Splide: Splide, Components: Components, options: Options ): ElementsComponent {
   const { on } = EventInterface( Splide );
   const { root } = Splide;
+  const { i18n } = options;
   const elements: ElementCollection = {} as ElementCollection;
 
   /**
@@ -120,7 +121,9 @@ export function Elements( Splide: Splide, Components: Components, options: Optio
    * Called when the component is mounted.
    */
   function mount(): void {
-    on( EVENT_REFRESH, refresh, DEFAULT_EVENT_PRIORITY - 2 );
+    const priority = DEFAULT_EVENT_PRIORITY - 2;
+    on( EVENT_REFRESH, destroy, priority );
+    on( EVENT_REFRESH, setup, priority );
     on( EVENT_UPDATED, update );
   }
 
@@ -133,14 +136,6 @@ export function Elements( Splide: Splide, Components: Components, options: Optio
     removeAttribute( [ root, track, list ], ALL_ATTRIBUTES.concat( 'style' ) );
   }
 
-  /**
-   * Recollects slide elements.
-   */
-  function refresh(): void {
-    destroy();
-    setup();
-  }
-
   /**
    * Updates the status of elements.
    */
@@ -189,7 +184,9 @@ export function Elements( Splide: Splide, Components: Components, options: Optio
     track.id = track.id || `${ id }-track`;
     list.id  = list.id || `${ id }-list`;
 
-    setAttribute( root, ARIA_ROLEDESCRIPTION, 'carousel' );
+    setAttribute( root, ARIA_ROLEDESCRIPTION, i18n.carousel );
+    setAttribute( root, ROLE, root.tagName !== 'SECTION' && options.role || null );
+    setAttribute( list, ROLE, 'none' );
   }
 
   /**

+ 18 - 7
src/js/components/Elements/test/attributes.test.ts

@@ -1,11 +1,11 @@
-import { ARIA_ROLEDESCRIPTION, TAB_INDEX } from '../../../constants/attributes';
-import { init } from '../../../test';
+import { ALL_ATTRIBUTES, ARIA_ROLEDESCRIPTION, ROLE, TAB_INDEX } from '../../../constants/attributes';
+import { buildHtml, init } from '../../../test';
 
 
 describe( 'Elements', () => {
   test( 'can assign aria attributes.', () => {
     const splide = init();
-
+    expect( splide.root.getAttribute( ROLE ) ).toBe( 'region' );
     expect( splide.root.getAttribute( ARIA_ROLEDESCRIPTION ) ).toBe( 'carousel' );
   } );
 
@@ -17,10 +17,21 @@ describe( 'Elements', () => {
 
     splide.destroy();
 
-    expect( root.getAttribute( ARIA_ROLEDESCRIPTION ) ).toBeNull();
-    expect( root.getAttribute( TAB_INDEX ) ).toBeNull();
+    const attributes = ALL_ATTRIBUTES.concat( 'style' );
+    const callback   = jest.fn();
+
+    [ root, track, list ].forEach( elm => {
+      attributes.forEach( attr => {
+        expect( elm.getAttribute( attr ) ).toBeNull();
+        callback();
+      } );
+    } );
+
+    expect( callback ).toHaveBeenCalledTimes( attributes.length * 3 );
+  } );
 
-    expect( track.getAttribute( 'style' ) ).toBeNull();
-    expect( list.getAttribute( 'style' ) ).toBeNull();
+  test( 'should not assign the role if the root element is section.', () => {
+    const splide = init( {}, { html: buildHtml( { tag: 'section' } ) } );
+    expect( splide.root.getAttribute( ROLE ) ).toBeNull();
   } );
 } );

+ 2 - 9
src/js/components/LazyLoad/LazyLoad.ts

@@ -79,7 +79,8 @@ export function LazyLoad( Splide: Splide, Components: Components, options: Optio
   function mount(): void {
     if ( options.lazyLoad ) {
       init();
-      on( EVENT_REFRESH, refresh );
+      on( EVENT_REFRESH, destroy );
+      on( EVENT_REFRESH, init );
 
       if ( ! isSequential ) {
         on( [ EVENT_MOUNTED, EVENT_REFRESH, EVENT_MOVED, EVENT_SCROLLED ], observe );
@@ -87,14 +88,6 @@ export function LazyLoad( Splide: Splide, Components: Components, options: Optio
     }
   }
 
-  /**
-   * Called when the slider is refreshed.
-   */
-  function refresh(): void {
-    destroy();
-    init();
-  }
-
   /**
    * Finds images that contain specific data attributes.
    */

+ 67 - 0
src/js/components/Live/Live.ts

@@ -0,0 +1,67 @@
+import { ARIA_ATOMIC, ARIA_LIVE } from '../../constants/attributes';
+import { EVENT_AUTOPLAY_PAUSE, EVENT_AUTOPLAY_PLAY } from '../../constants/events';
+import { EventInterface } from '../../constructors';
+import { Splide } from '../../core/Splide/Splide';
+import { BaseComponent, Components, Options } from '../../types';
+import { apply, setAttribute } from '../../utils';
+
+
+/**
+ * The interface for the Live component.
+ *
+ * @since 3.7.0
+ */
+export interface LiveComponent extends BaseComponent {
+  disable( disabled: boolean ): void;
+}
+
+/**
+ * The component for implementing Live Region to the slider.
+ *
+ * @link https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions
+ *
+ * @since 3.7.0
+ *
+ * @param Splide     - A Splide instance.
+ * @param Components - A collection of components.
+ * @param options    - Options.
+ *
+ * @return A Live component object.
+ */
+export function Live( Splide: Splide, Components: Components, options: Options ): LiveComponent {
+  const { on } = EventInterface( Splide );
+  const { list } = Components.Elements;
+  const { live } = options;
+
+  /**
+   * Called when the component is mounted.
+   * Explicitly sets `aria-atomic` to avoid SR from reading the content twice.
+   *
+   * @link https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-atomic
+   */
+  function mount(): void {
+    if ( live ) {
+      setAttribute( list, ARIA_ATOMIC, false );
+      disable( ! Components.Autoplay.isPaused() );
+      on( EVENT_AUTOPLAY_PLAY, apply( disable, true ) );
+      on( EVENT_AUTOPLAY_PAUSE, apply( disable, false ) );
+    }
+  }
+
+  /**
+   * Disables/enables the live region.
+   * Does nothing when the `live` option is not enabled.
+   *
+   * @param disabled - `true` to disable the live region or `false` to enable it again.
+   */
+  function disable( disabled: boolean ): void {
+    if ( live ) {
+      setAttribute( list, ARIA_LIVE, disabled ? 'off' : 'polite' );
+    }
+  }
+
+  return {
+    mount,
+    disable,
+  };
+}

+ 72 - 0
src/js/components/Live/test/general.test.ts

@@ -0,0 +1,72 @@
+import { ARIA_ATOMIC, ARIA_LIVE } from '../../../constants/attributes';
+import { init } from '../../../test';
+
+
+describe( 'Live', () => {
+  test( 'can assign aria-atomic="false" to the list element.', () => {
+    const splide = init( { live: true } );
+    expect( splide.Components.Elements.list.getAttribute( ARIA_ATOMIC ) ).toBe( 'false' );
+  } );
+
+  test( 'can assign aria-live="polite" to the list element.', () => {
+    const splide = init( { live: true } );
+    expect( splide.Components.Elements.list.getAttribute( ARIA_LIVE ) ).toBe( 'polite' );
+  } );
+
+  test( 'can assign aria-live="off" to the list element when the autoplay is `true`.', () => {
+    const splide = init( { live: true, autoplay: true } );
+    expect( splide.Components.Elements.list.getAttribute( ARIA_LIVE ) ).toBe( 'off' );
+  } );
+
+  test( 'can assign aria-live="polite" to the list element when the autoplay is `"pause".`', () => {
+    const splide = init( { live: true, autoplay: 'pause' } );
+    expect( splide.Components.Elements.list.getAttribute( ARIA_LIVE ) ).toBe( 'polite' );
+  } );
+
+  test( 'can change aria-live to "off" when autoplay starts, or to "polite" when autoplay stops.', () => {
+    const splide = init( { live: true, autoplay: 'pause' } );
+    const { list } = splide.Components.Elements;
+    const { play, pause } = splide.Components.Autoplay;
+
+    expect( list.getAttribute( ARIA_LIVE ) ).toBe( 'polite' );
+
+    play();
+    expect( list.getAttribute( ARIA_LIVE ) ).toBe( 'off' );
+
+    pause();
+    expect( list.getAttribute( ARIA_LIVE ) ).toBe( 'polite' );
+
+    play();
+    expect( list.getAttribute( ARIA_LIVE ) ).toBe( 'off' );
+  } );
+
+  test( 'can toggle aria-live by `disable()`', () => {
+    const splide = init( { live: true } );
+    const { list } = splide.Components.Elements;
+    const { disable } = splide.Components.Live;
+
+    disable( true );
+    expect( list.getAttribute( ARIA_LIVE ) ).toBe( 'off' );
+
+    disable( false );
+    expect( list.getAttribute( ARIA_LIVE ) ).toBe( 'polite' );
+
+    disable( true );
+    expect( list.getAttribute( ARIA_LIVE ) ).toBe( 'off' );
+  } );
+
+  test( 'should do nothing when the `live` option is false.', () => {
+    const splide = init( { live: false } );
+    const { list } = splide.Components.Elements;
+    const { disable } = splide.Components.Live;
+
+    expect( list.getAttribute( ARIA_ATOMIC ) ).toBeNull();
+    expect( list.getAttribute( ARIA_LIVE ) ).toBeNull();
+
+    disable( true );
+    expect( list.getAttribute( ARIA_LIVE ) ).toBeNull();
+
+    disable( false );
+    expect( list.getAttribute( ARIA_LIVE ) ).toBeNull();
+  } );
+} );

+ 6 - 7
src/js/components/Pagination/Pagination.ts

@@ -12,7 +12,7 @@ import { EventInterface } from '../../constructors';
 import { Splide } from '../../core/Splide/Splide';
 import { BaseComponent, Components, Options } from '../../types';
 import {
-  addClass,
+  addClass, apply,
   ceil,
   create,
   empty,
@@ -129,14 +129,13 @@ export function Pagination( Splide: Splide, Components: Components, options: Opt
     list = create( 'ul', classes.pagination, parent );
 
     for ( let i = 0; i < max; i++ ) {
-      const li       = create( 'li', null, list );
-      const button   = create( 'button', { class: classes.page, type: 'button' }, li );
-      const controls = Slides.getIn( i ).map( Slide => Slide.slide.id );
-      const text     = ! hasFocus() && perPage > 1 ? i18n.pageX : i18n.slideX;
+      const li     = create( 'li', null, list );
+      const button = create( 'button', { class: classes.page, type: 'button' }, li );
+      const text   = ! hasFocus() && perPage > 1 ? i18n.pageX : i18n.slideX;
 
-      bind( button, 'click', onClick.bind( null, i ) );
+      bind( button, 'click', apply( onClick, i ) );
 
-      setAttribute( button, ARIA_CONTROLS, controls.join( ' ' ) );
+      setAttribute( button, ARIA_CONTROLS, Components.Elements.list.id );
       setAttribute( button, ARIA_LABEL, format( text, i + 1 ) );
 
       items.push( { li, button, page: i } );

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

@@ -85,7 +85,7 @@ export interface  SlideComponent extends BaseComponent {
 export function Slide( Splide: Splide, index: number, slideIndex: number, slide: HTMLElement ): SlideComponent {
   const { on, emit, bind, destroy: destroyEvents } = EventInterface( Splide );
   const { Components, root, options } = Splide;
-  const { isNavigation, updateOnMove } = options;
+  const { isNavigation, updateOnMove, i18n } = options;
   const { resolve } = Components.Direction;
   const styles         = getAttribute( slide, 'style' );
   const isClone        = slideIndex > -1;
@@ -104,7 +104,8 @@ export function Slide( Splide: Splide, index: number, slideIndex: number, slide:
     if ( ! isClone ) {
       slide.id = `${ root.id }-slide${ pad( index + 1 ) }`;
       setAttribute( slide, ROLE, 'group' );
-      setAttribute( slide, ARIA_ROLEDESCRIPTION, 'slide' );
+      setAttribute( slide, ARIA_ROLEDESCRIPTION, i18n.slide );
+      setAttribute( slide, ARIA_LABEL, format( i18n.slideLabel, [ index + 1, Splide.length ] ) );
     }
 
     listen();
@@ -142,7 +143,7 @@ export function Slide( Splide: Splide, index: number, slideIndex: number, slide:
    */
   function initNavigation(): void {
     const idx      = isClone ? slideIndex : index;
-    const label    = format( options.i18n.slideX, idx + 1 );
+    const label    = format( i18n.slideX, idx + 1 );
     const controls = Splide.splides.map( target => target.splide.root.id ).join( ' ' );
 
     setAttribute( slide, ARIA_LABEL, label );

+ 4 - 11
src/js/components/Slides/Slides.ts

@@ -4,7 +4,7 @@ import { Splide } from '../../core/Splide/Splide';
 import { AnyFunction, BaseComponent, Components, Options } from '../../types';
 import {
   addClass,
-  append,
+  append, apply,
   before,
   between,
   empty,
@@ -88,7 +88,8 @@ export function Slides( Splide: Splide, Components: Components, options: Options
    */
   function mount(): void {
     init();
-    on( EVENT_REFRESH, refresh );
+    on( EVENT_REFRESH, destroy );
+    on( EVENT_REFRESH, init );
     on( [ EVENT_MOUNTED, EVENT_REFRESH ], () => {
       Slides.sort( ( Slide1, Slide2 ) => Slide1.index - Slide2.index );
     } );
@@ -109,14 +110,6 @@ export function Slides( Splide: Splide, Components: Components, options: Options
     empty( Slides );
   }
 
-  /**
-   * Discards all Slide components and regenerates them.
-   */
-  function refresh(): void {
-    destroy();
-    init();
-  }
-
   /**
    * Manually updates the status of all slides.
    */
@@ -189,7 +182,7 @@ export function Slides( Splide: Splide, Components: Components, options: Options
         const ref = slides[ index ];
         ref ? before( slide, ref ) : append( list, slide );
         addClass( slide, options.classes.slide );
-        observeImages( slide, emit.bind( null, EVENT_RESIZE ) );
+        observeImages( slide, apply( emit, EVENT_RESIZE ) );
       }
     } );
 

+ 1 - 0
src/js/components/index.ts

@@ -15,4 +15,5 @@ export { Keyboard }   from './Keyboard/Keyboard';
 export { LazyLoad }   from './LazyLoad/LazyLoad';
 export { Pagination } from './Pagination/Pagination';
 export { Sync }       from './Sync/Sync';
+export { Live }       from './Live/Live';
 export { Wheel }      from './Wheel/Wheel';

+ 1 - 0
src/js/components/types.ts

@@ -17,5 +17,6 @@ export type { LazyLoadComponent }   from './LazyLoad/LazyLoad';
 export type { PaginationComponent } from './Pagination/Pagination';
 export type { SyncComponent }       from './Sync/Sync';
 export type { WheelComponent }      from './Wheel/Wheel';
+export type { LiveComponent }       from './Live/Live';
 
 export type { PaginationData, PaginationItem } from './Pagination/Pagination';

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

@@ -11,6 +11,7 @@ import { I18N } from './i18n';
  */
 export const DEFAULTS: Options = {
   type             : 'slide',
+  role             : 'region',
   speed            : 400,
   waitForTransition: true,
   perPage          : 1,

+ 11 - 8
src/js/constants/i18n.ts

@@ -4,12 +4,15 @@
  * @since 3.0.0
  */
 export const I18N = {
-  prev  : 'Previous slide',
-  next  : 'Next slide',
-  first : 'Go to first slide',
-  last  : 'Go to last slide',
-  slideX: 'Go to slide %s',
-  pageX : 'Go to page %s',
-  play  : 'Start autoplay',
-  pause : 'Pause autoplay',
+  prev      : 'Previous slide',
+  next      : 'Next slide',
+  first     : 'Go to first slide',
+  last      : 'Go to last slide',
+  slideX    : 'Go to slide %s',
+  pageX     : 'Go to page %s',
+  play      : 'Start autoplay',
+  pause     : 'Pause autoplay',
+  carousel  : 'carousel',
+  slide     : 'slide',
+  slideLabel: '%s of %s', // [ slide number ] / [ slide size ]
 };

+ 2 - 2
src/js/constructors/EventInterface/EventInterface.ts

@@ -1,7 +1,7 @@
 import { EVENT_DESTROY } from '../../constants/events';
 import { Splide } from '../../core/Splide/Splide';
 import { AnyFunction, EventMap } from '../../types';
-import { forEach } from '../../utils';
+import { apply, forEach } from '../../utils';
 import { EventBusCallback } from '../EventBus/EventBus';
 
 
@@ -139,7 +139,7 @@ export function EventInterface( Splide: Splide ): EventInterfaceObject {
   ): void {
     forEach( targets, target => {
       if ( target ) {
-        events.split( ' ' ).forEach( iteratee.bind( null, target ) );
+        events.split( ' ' ).forEach( apply( iteratee, target ) );
       }
     } );
   }

+ 4 - 2
src/js/test/fixtures/html.ts

@@ -4,6 +4,7 @@ import { URL } from './constants';
 
 
 export interface BuildHtmlArgs {
+  tag?: string;
   id?: string;
   length?: number;
   arrows?: boolean;
@@ -25,6 +26,7 @@ export interface BuildHtmlArgs {
  */
 export function buildHtml( args: BuildHtmlArgs = {} ): string {
   const {
+    tag = 'div',
     id,
     length = 10,
     arrows,
@@ -38,7 +40,7 @@ export function buildHtml( args: BuildHtmlArgs = {} ): string {
   } = args;
 
   return `
-<div class="splide"${ id ? ` id=${ id }` : '' }${ json ? ` data-splide='${ json }'` : '' }>
+<${ tag } class="splide"${ id ? ` id=${ id }` : '' }${ json ? ` data-splide='${ json }'` : '' }>
   <div class="splide__track">
     <ul class="splide__list">
       ${ generateSlides( length, src, dataSrc, dataSrcset, dataInterval ) }
@@ -48,7 +50,7 @@ export function buildHtml( args: BuildHtmlArgs = {} ): string {
   ${ arrows ? HTML_ARROWS : '' }
   ${ progress ? HTML_PROGRESS : '' }
   ${ autoplay ? HTML_AUTOPLAY : '' }
-</div>
+</${ tag }>
 `;
 }
 

+ 67 - 0
src/js/test/php/examples/live-regions.php

@@ -0,0 +1,67 @@
+<?php
+require_once '../parts.php';
+require_once '../settings.php';
+
+$settings = get_settings();
+?>
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width,initial-scale=1">
+  <title>Live Regions</title>
+
+  <link rel="stylesheet" href="../../../../../dist/css/themes/splide-<?php echo $settings['theme'] ?>.min.css">
+  <link rel="stylesheet" href="../../assets/css/styles.css">
+  <script src="../../../../../dist/js/splide.js"></script>
+
+  <script>
+    document.addEventListener( 'DOMContentLoaded', function () {
+      var splide = new Splide( '#splide01', {
+        width: 800,
+	      rewind: true,
+	      // autoplay: true,
+	      // live: false,
+	      slideFocus: false,
+      } );
+
+      splide.mount();
+    } );
+  </script>
+
+	<style>
+      .splide__slide {
+          /*display: none;*/
+      }
+
+		.splide__slide.is-active {
+				display: block;
+		}
+	</style>
+</head>
+<body>
+
+<div id="splide01" class="splide" aria-label="Slider With Live Region">
+	<div class="splide__track">
+		<ul class="splide__list">
+			<?php
+			for ( $i = 0; $i < 5; $i++ ) {
+				echo '<li class="splide__slide">';
+				printf( '<img src="../../assets/images/pics/slide%1$02d.jpg" alt="Alt text on image %1$d">', $i + 1 );
+				printf( '<h3>Slide %02d</h3>', $i + 1 );
+				print( '<p>Slide description</p>' );
+				echo '</li>' . PHP_EOL;
+			}
+			?>
+		</ul>
+	</div>
+
+	<div class="splide__autoplay">
+		<button class="splide__play">Play</button>
+		<button class="splide__pause">Pause</button>
+	</div>
+</div>
+
+</body>
+</html>

+ 2 - 0
src/js/types/components.ts

@@ -9,6 +9,7 @@ import { ElementsComponent } from '../components/Elements/Elements';
 import { KeyboardComponent } from '../components/Keyboard/Keyboard';
 import { LayoutComponent } from '../components/Layout/Layout';
 import { LazyLoadComponent } from '../components/LazyLoad/LazyLoad';
+import { LiveComponent } from '../components/Live/Live';
 import { MoveComponent } from '../components/Move/Move';
 import { OptionsComponent } from '../components/Options/Options';
 import { PaginationComponent } from '../components/Pagination/Pagination';
@@ -44,5 +45,6 @@ export interface Components {
   Pagination: PaginationComponent;
   Sync: SyncComponent;
   Wheel: WheelComponent;
+  Live: LiveComponent;
   Transition: TransitionComponent;
 }

+ 1 - 2
src/js/types/index.ts

@@ -2,5 +2,4 @@ export * from './components';
 export * from './events';
 export * from './general';
 export * from './options';
-
-
+export * from './utils';

+ 16 - 1
src/js/types/options.ts

@@ -1,3 +1,6 @@
+import { I18N } from '../constants/i18n';
+
+
 /**
  * The interface for options.
  *
@@ -12,6 +15,12 @@ export interface Options extends ResponsiveOptions {
    */
   type?: string;
 
+  /**
+   * The `role` attribute for the root element.
+   * If the tag is `<section>`, this value will not be used. The default value is `'region'`.
+   */
+  role?: string;
+
   /**
    * Determines whether to disable any actions while the slider is transitioning.
    * Even if `false`, the slider forcibly waits for transition on the loop points.
@@ -157,6 +166,12 @@ export interface Options extends ResponsiveOptions {
    */
   noDrag?: string;
 
+  /**
+   * Enables the live region by `aria-live`.
+   * If `true`, screen readers will read a content of each slide whenever slide changes.
+   */
+  live?: boolean;
+
   /**
    * Determines whether to use the Transition component or not.
    */
@@ -189,7 +204,7 @@ export interface Options extends ResponsiveOptions {
   /**
    * The collection of i18n strings.
    */
-  i18n?: Record<string, string>;
+  i18n?: Record<keyof typeof I18N | string, string>;
 }
 
 /**

+ 32 - 0
src/js/types/utils.ts

@@ -0,0 +1,32 @@
+/**
+ * Casts T to U.
+ *
+ * @internal
+ */
+export type Cast<T, U> = T extends U ? T : U;
+
+/**
+ * Pushes U to tuple T.
+ *
+ * @internal
+ */
+export type Push<T extends any[], U = any> = [ ...T, U ];
+
+/**
+ * Removes the first type from the tuple T.
+ *
+ * @internal
+ */
+export type Shift<T extends any[]> = ( ( ...args: T ) => any ) extends ( arg: any, ...args: infer A ) => any
+  ? A
+  : never;
+
+/**
+ * Removes the N types from the tuple T.
+ *
+ * @internal
+ */
+export type ShiftN<T extends any[], N extends number, C extends any[] = []> = {
+  0: T,
+  1: ShiftN<Shift<T>, N, Push<C>>,
+}[ C['length'] extends N ? 0 : 1 ] extends infer A ? Cast<A, any[]> : never;

+ 25 - 0
src/js/utils/function/apply/apply.test.ts

@@ -0,0 +1,25 @@
+import { apply } from './apply';
+
+
+describe( 'apply', () => {
+  test( 'can bind arguments to the function.', () => {
+    function sum( a: number, b: number, c = 0, d = 0 ): number {
+      return a + b + c + d;
+    }
+
+    // The type should be ( b: number, c?: number, d?: number ) => number.
+    const sum1 = apply( sum, 1 );
+    const sum2 = apply( sum, 1, 1 );
+    const sum3 = apply( sum, 1, 1, 1 );
+    const sum4 = apply( sum, 1, 1, 1, 1 );
+
+    expect( sum1( 1, 1, 1 ) ).toBe( 4 );
+    expect( sum2( 1, 1 ) ).toBe( 4 );
+    expect( sum3( 1 ) ).toBe( 4 );
+    expect( sum4() ).toBe( 4 );
+
+    expect( sum1( 2 ) ).toBe( 3 ); // 1, 2, 0, 0
+    expect( sum1( 2, 2 ) ).toBe( 5 ); // 1, 2, 2, 0
+    expect( sum1( 2, 2, 2 ) ).toBe( 7 ); // 1, 2, 2, 2
+  } );
+} );

+ 28 - 0
src/js/utils/function/apply/apply.ts

@@ -0,0 +1,28 @@
+import { AnyFunction, ShiftN } from '../../../types';
+import { slice } from '../../arrayLike';
+
+
+/**
+ * Create a function where provided arguments are bound.
+ * `this` parameter will be always null.
+ *
+ * @param func - A function.
+ * @param args - Arguments to bind to the function.
+ *
+ * @return A function where arguments are bound.
+ */
+export function apply<F extends AnyFunction, A extends any[]>(
+  func: F,
+  ...args: A
+): ( ...args: ShiftN<Parameters<F>, A["length"]> ) => ReturnType<F>;
+
+/**
+ * Create a function where provided arguments are bound.
+ * `this` parameter will be always null.
+ *
+ * @param func - A function.
+ */
+export function apply( func: AnyFunction ): any {
+  // eslint-disable-next-line prefer-rest-params, prefer-spread
+  return func.bind( null, ...slice( arguments, 1 ) );
+}

+ 1 - 0
src/js/utils/function/index.ts

@@ -1,3 +1,4 @@
+export { apply }    from './apply/apply';
 export { nextTick } from './nextTick/nextTick';
 export { noop }     from './noop/noop';
 export { raf }      from './raf/raf';

+ 1 - 3
src/js/utils/function/nextTick/nextTick.ts

@@ -6,6 +6,4 @@ import { AnyFunction } from '../../../types';
  *
  * @param callback - A callback function.
  */
-export function nextTick( callback: AnyFunction ): void {
-  setTimeout( callback );
-}
+export const nextTick: ( callback: AnyFunction ) => ReturnType<typeof setTimeout> = setTimeout;

+ 1 - 2
src/js/utils/object/merge/merge.ts

@@ -1,3 +1,4 @@
+import { Cast } from '../../../types';
 import { isArray, isObject } from '../../type/type';
 import { forOwn } from '../forOwn/forOwn';
 
@@ -22,8 +23,6 @@ export type Merge<T extends object, U extends object> = Omit<T, keyof U> & {
     : U[ K ];
 } & Omit<U, keyof T>;
 
-type Cast<T, U> = T extends U ? T : U;
-
 /**
  * Recursively merges source properties to the object.
  * Be aware that this method does not merge arrays. They are just duplicated by `slice()`.

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików