Pagination.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import { ARIA_CONTROLS, ARIA_CURRENT, ARIA_LABEL } from '../../constants/attributes';
  2. import { CLASS_ACTIVE } from '../../constants/classes';
  3. import {
  4. EVENT_MOVE,
  5. EVENT_PAGINATION_MOUNTED,
  6. EVENT_PAGINATION_UPDATED,
  7. EVENT_REFRESH,
  8. EVENT_SCROLLED,
  9. EVENT_UPDATED,
  10. } from '../../constants/events';
  11. import { EventInterface } from '../../constructors';
  12. import { Splide } from '../../core/Splide/Splide';
  13. import { BaseComponent, Components, Options } from '../../types';
  14. import {
  15. addClass, apply,
  16. ceil,
  17. create,
  18. empty,
  19. focus,
  20. format,
  21. remove,
  22. removeAttribute,
  23. removeClass,
  24. setAttribute,
  25. } from '../../utils';
  26. /**
  27. * The interface for the Pagination component.
  28. *
  29. * @since 3.0.0
  30. */
  31. export interface PaginationComponent extends BaseComponent {
  32. items: PaginationItem[];
  33. getAt( index: number ): PaginationItem;
  34. update(): void;
  35. }
  36. /**
  37. * The interface for data of the pagination.
  38. *
  39. * @since 3.0.0
  40. */
  41. export interface PaginationData {
  42. list: HTMLUListElement;
  43. items: PaginationItem[];
  44. }
  45. /**
  46. * The interface for each pagination item.
  47. *
  48. * @since 3.0.0
  49. */
  50. export interface PaginationItem {
  51. li: HTMLLIElement;
  52. button: HTMLButtonElement;
  53. page: number;
  54. }
  55. /**
  56. * The component for handling previous and next arrows.
  57. *
  58. * @since 3.0.0
  59. *
  60. * @param Splide - A Splide instance.
  61. * @param Components - A collection of components.
  62. * @param options - Options.
  63. *
  64. * @return A Arrows component object.
  65. */
  66. export function Pagination( Splide: Splide, Components: Components, options: Options ): PaginationComponent {
  67. const { on, emit, bind, unbind } = EventInterface( Splide );
  68. const { Slides, Elements, Controller } = Components;
  69. const { hasFocus, getIndex } = Controller;
  70. /**
  71. * Stores all pagination items.
  72. */
  73. const items: PaginationItem[] = [];
  74. /**
  75. * The pagination element.
  76. */
  77. let list: HTMLUListElement;
  78. /**
  79. * Called when the component is mounted.
  80. */
  81. function mount(): void {
  82. init();
  83. on( [ EVENT_UPDATED, EVENT_REFRESH ], init );
  84. on( [ EVENT_MOVE, EVENT_SCROLLED ], update );
  85. }
  86. /**
  87. * Initializes the pagination.
  88. */
  89. function init(): void {
  90. destroy();
  91. if ( options.pagination && Slides.isEnough() ) {
  92. createPagination();
  93. emit( EVENT_PAGINATION_MOUNTED, { list, items }, getAt( Splide.index ) );
  94. update();
  95. }
  96. }
  97. /**
  98. * Destroys the component.
  99. */
  100. function destroy(): void {
  101. if ( list ) {
  102. remove( list );
  103. items.forEach( item => { unbind( item.button, 'click' ) } );
  104. empty( items );
  105. list = null;
  106. }
  107. }
  108. /**
  109. * Creates the pagination element and appends it to the slider.
  110. */
  111. function createPagination(): void {
  112. const { length } = Splide;
  113. const { classes, i18n, perPage } = options;
  114. const parent = options.pagination === 'slider' && Elements.slider || Elements.root;
  115. const max = hasFocus() ? length : ceil( length / perPage );
  116. list = create( 'ul', classes.pagination, parent );
  117. for ( let i = 0; i < max; i++ ) {
  118. const li = create( 'li', null, list );
  119. const button = create( 'button', { class: classes.page, type: 'button' }, li );
  120. const text = ! hasFocus() && perPage > 1 ? i18n.pageX : i18n.slideX;
  121. bind( button, 'click', apply( onClick, i ) );
  122. setAttribute( button, ARIA_CONTROLS, Components.Elements.list.id );
  123. setAttribute( button, ARIA_LABEL, format( text, i + 1 ) );
  124. items.push( { li, button, page: i } );
  125. }
  126. }
  127. /**
  128. * Called when the user clicks each pagination dot.
  129. * Moves the focus to the active slide for accessibility.
  130. *
  131. * @link https://www.w3.org/WAI/tutorials/carousels/functionality/
  132. *
  133. * @param page - A clicked page index.
  134. */
  135. function onClick( page: number ): void {
  136. Controller.go( `>${ page }`, true, () => {
  137. const Slide = Slides.getAt( Controller.toIndex( page ) );
  138. Slide && focus( Slide.slide );
  139. } );
  140. }
  141. /**
  142. * Returns the pagination item at the specified index.
  143. *
  144. * @param index - An index.
  145. *
  146. * @return A pagination item object if available, or otherwise `undefined`.
  147. */
  148. function getAt( index: number ): PaginationItem | undefined {
  149. return items[ Controller.toPage( index ) ];
  150. }
  151. /**
  152. * Updates the pagination status.
  153. */
  154. function update(): void {
  155. const prev = getAt( getIndex( true ) );
  156. const curr = getAt( getIndex() );
  157. if ( prev ) {
  158. removeClass( prev.button, CLASS_ACTIVE );
  159. removeAttribute( prev.button, ARIA_CURRENT );
  160. }
  161. if ( curr ) {
  162. addClass( curr.button, CLASS_ACTIVE );
  163. setAttribute( curr.button, ARIA_CURRENT, true );
  164. }
  165. emit( EVENT_PAGINATION_UPDATED, { list, items }, prev, curr );
  166. }
  167. return {
  168. items,
  169. mount,
  170. destroy,
  171. getAt,
  172. update,
  173. };
  174. }