123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- 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_END_INDEX_CHANGED,
- EVENT_MOVE,
- EVENT_PAGINATION_MOUNTED,
- EVENT_PAGINATION_UPDATED,
- EVENT_REFRESH,
- EVENT_SCROLL,
- EVENT_SCROLLED,
- EVENT_UPDATED,
- } from '../../constants/events';
- import { BaseComponent, ComponentConstructor, Options } from '../../types';
- import {
- addClass,
- apply,
- ceil,
- create,
- display,
- empty,
- focus,
- format,
- prevent,
- removeAttribute,
- removeClass,
- removeNode,
- setAttribute,
- slice,
- } from '@splidejs/utils';
- /**
- * The interface for the Pagination component.
- *
- * @since 3.0.0
- */
- export interface PaginationComponent extends BaseComponent {
- readonly items: PaginationItem[];
- getAt(index: number): PaginationItem;
- update(): void;
- }
- /**
- * The interface for data of the pagination.
- *
- * @since 3.0.0
- */
- export interface PaginationData {
- readonly list: HTMLUListElement;
- readonly items: PaginationItem[];
- }
- /**
- * The interface for each pagination item.
- *
- * @since 3.0.0
- */
- export interface PaginationItem {
- readonly li: HTMLLIElement;
- readonly button: HTMLButtonElement;
- readonly page: number;
- }
- /**
- * The component for the pagination UI (a slide picker).
- *
- * @since 3.0.0
- *
- * @param Splide - A Splide instance.
- * @param Components - A collection of components.
- * @param options - Options.
- * @param event - An EventInterface instance.
- *
- * @return A Pagination component object.
- */
- export const Pagination: ComponentConstructor<PaginationComponent> = (Splide, Components, options, event) => {
- 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, EVENT_END_INDEX_CHANGED], mount);
- const { pagination: enabled = true } = options;
- 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) {
- removeNode(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, paginationKeyboard = true } = options;
- const max = hasFocus() ? Controller.getEnd() + 1 : ceil(length / perPage);
- const dir = getDirection();
- list = placeholder || create('ul', classes.pagination, Elements.track.parentElement);
- addClass(list, (paginationClasses = `${ CLASS_PAGINATION }--${ dir }`));
- setAttribute(list, ROLE, 'tablist');
- setAttribute(list, ARIA_LABEL, i18n.select);
- setAttribute(list, ARIA_ORIENTATION, dir === 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', () => go(`>${ i }`));
- 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 any key is pressed on the pagination.
- *
- * @link https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/#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 } = 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.
- *
- * @return The 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,
- };
- };
|