123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- import { ARROW_LEFT, ARROW_RIGHT } from '../../constants/arrows';
- import {
- ARIA_CONTROLS,
- ARIA_LABEL,
- ARIA_ORIENTATION,
- ARIA_SELECTED,
- ROLE,
- TAB_INDEX,
- } from '../../constants/attributes';
- import { CLASS_ACTIVE, CLASS_PAGINATION } from '../../constants/classes';
- import { TTB } from '../../constants/directions';
- import {
- EVENT_MOVE,
- EVENT_PAGINATION_MOUNTED,
- EVENT_PAGINATION_UPDATED,
- EVENT_REFRESH,
- EVENT_SCROLL,
- EVENT_SCROLLED,
- EVENT_UPDATED,
- } from '../../constants/events';
- import { EventInterface } from '../../constructors';
- import { Splide } from '../../core/Splide/Splide';
- import { BaseComponent, Components, Options } from '../../types';
- import {
- addClass,
- apply,
- ceil,
- create, display,
- empty,
- focus,
- format,
- prevent,
- remove,
- removeAttribute,
- removeClass,
- setAttribute,
- slice,
- } from '../../utils';
- import { normalizeKey } from '../../utils/dom/normalizeKey/normalizeKey';
- /**
- * The interface for the Pagination component.
- *
- * @since 3.0.0
- */
- export interface PaginationComponent extends BaseComponent {
- items: PaginationItem[];
- getAt( index: number ): PaginationItem;
- update(): void;
- }
- /**
- * The interface for data of the pagination.
- *
- * @since 3.0.0
- */
- export interface PaginationData {
- list: HTMLUListElement;
- items: PaginationItem[];
- }
- /**
- * The interface for each pagination item.
- *
- * @since 3.0.0
- */
- export interface PaginationItem {
- li: HTMLLIElement;
- button: HTMLButtonElement;
- page: number;
- }
- /**
- * The component for the pagination UI (a slide picker).
- *
- * @link https://www.w3.org/TR/2021/NOTE-wai-aria-practices-1.2-20211129/#grouped-carousel-elements
- * @since 3.0.0
- *
- * @param Splide - A Splide instance.
- * @param Components - A collection of components.
- * @param options - Options.
- *
- * @return A Pagination component object.
- */
- export function Pagination( Splide: Splide, Components: Components, options: Options ): PaginationComponent {
- const event = EventInterface( Splide );
- const { on, emit, bind } = event;
- const { Slides, Elements, Controller } = Components;
- const { hasFocus, getIndex, go } = Controller;
- const { resolve } = Components.Direction;
- const { pagination: placeholder } = Elements;
- /**
- * Stores all pagination items.
- */
- const items: PaginationItem[] = [];
- /**
- * The pagination element.
- */
- let list: HTMLUListElement | null;
- /**
- * Holds modifier classes.
- */
- let paginationClasses: string;
- /**
- * Called when the component is mounted.
- */
- function mount(): void {
- destroy();
- on( [ EVENT_UPDATED, EVENT_REFRESH ], mount );
- const enabled = options.pagination && Slides.isEnough();
- placeholder && display( placeholder, enabled ? '' : 'none' );
- if ( enabled ) {
- on( [ EVENT_MOVE, EVENT_SCROLL, EVENT_SCROLLED ], update );
- createPagination();
- update();
- emit( EVENT_PAGINATION_MOUNTED, { list, items }, getAt( Splide.index ) );
- }
- }
- /**
- * Destroys the component.
- */
- function destroy(): void {
- if ( list ) {
- remove( placeholder ? slice( list.children ) : list );
- removeClass( list, paginationClasses );
- empty( items );
- list = null;
- }
- event.destroy();
- }
- /**
- * Creates the pagination element and appends it to the slider.
- */
- function createPagination(): void {
- const { length } = Splide;
- const { classes, i18n, perPage } = options;
- const max = hasFocus() ? length : ceil( length / perPage );
- list = placeholder || create( 'ul', classes.pagination, Elements.track.parentElement );
- addClass( list, ( paginationClasses = `${ CLASS_PAGINATION }--${ getDirection() }` ) );
- setAttribute( list, ROLE, 'tablist' );
- setAttribute( list, ARIA_LABEL, i18n.select );
- setAttribute( list, ARIA_ORIENTATION, getDirection() === TTB ? 'vertical' : '' );
- 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', apply( onClick, i ) );
- if ( options.paginationKeyboard ) {
- bind( button, 'keydown', apply( onKeydown, i ) );
- }
- setAttribute( li, ROLE, 'presentation' );
- setAttribute( button, ROLE, 'tab' );
- setAttribute( button, ARIA_CONTROLS, controls.join( ' ' ) );
- setAttribute( button, ARIA_LABEL, format( text, i + 1 ) );
- setAttribute( button, TAB_INDEX, -1 );
- items.push( { li, button, page: i } );
- }
- }
- /**
- * Called when the user clicks each pagination dot.
- * Moves the focus to the active slide for accessibility.
- *
- * @link https://www.w3.org/WAI/tutorials/carousels/functionality/
- *
- * @param page - A clicked page index.
- */
- function onClick( page: number ): void {
- go( `>${ page }`, true );
- }
- /**
- * Called when any key is pressed on the pagination.
- *
- * @link https://www.w3.org/TR/2021/NOTE-wai-aria-practices-1.2-20211129/#keyboard-interaction-21
- *
- * @param page - A page index.
- * @param e - A KeyboardEvent object.
- */
- function onKeydown( page: number, e: KeyboardEvent ): void {
- const { length } = items;
- const key = normalizeKey( e );
- const dir = getDirection();
- let nextPage = -1;
- if ( key === resolve( ARROW_RIGHT, false, dir ) ) {
- nextPage = ++page % length;
- } else if ( key === resolve( ARROW_LEFT, false, dir ) ) {
- nextPage = ( --page + length ) % length;
- } else if ( key === 'Home' ) {
- nextPage = 0;
- } else if ( key === 'End' ) {
- nextPage = length - 1;
- }
- const item = items[ nextPage ];
- if ( item ) {
- focus( item.button );
- go( `>${ nextPage }` );
- prevent( e, true );
- }
- }
- /**
- * Returns the latest direction for pagination.
- */
- function getDirection(): Options['direction'] {
- return options.paginationDirection || options.direction;
- }
- /**
- * Returns the pagination item at the specified index.
- *
- * @param index - An index.
- *
- * @return A pagination item object if available, or otherwise `undefined`.
- */
- function getAt( index: number ): PaginationItem | undefined {
- return items[ Controller.toPage( index ) ];
- }
- /**
- * Updates the pagination status.
- */
- function update(): void {
- const prev = getAt( getIndex( true ) );
- const curr = getAt( getIndex() );
- if ( prev ) {
- const { button } = prev;
- removeClass( button, CLASS_ACTIVE );
- removeAttribute( button, ARIA_SELECTED );
- setAttribute( button, TAB_INDEX, -1 );
- }
- if ( curr ) {
- const { button } = curr;
- addClass( button, CLASS_ACTIVE );
- setAttribute( button, ARIA_SELECTED, true );
- setAttribute( button, TAB_INDEX, '' );
- }
- emit( EVENT_PAGINATION_UPDATED, { list, items }, prev, curr );
- }
- return {
- items,
- mount,
- destroy,
- getAt,
- update,
- };
- }
|