VUE

usePagination Composable

Reactive pagination logic with page size control and range calculation

Vue3ComposablesPaginationUIArrays

Code

import { ref, computed, type Ref } from 'vue';

export function usePagination<T>(
  items: Ref<T[]>,
  options: {
    initialPage?: number;
    initialPageSize?: number;
    maxPageNumbers?: number;
  } = { initialPage: 1, initialPageSize: 10, maxPageNumbers: 5 }
) {
  const currentPage = ref(options.initialPage ?? 1);
  const pageSize = ref(options.initialPageSize ?? 10);
  const maxPageNumbers = ref(options.maxPageNumbers ?? 5);

  // Total items count
  const totalItems = computed(() => items.value.length);

  // Total pages count
  const totalPages = computed(() => {
    return Math.ceil(totalItems.value / pageSize.value);
  });

  // Current page items
  const paginatedItems = computed(() => {
    const startIndex = (currentPage.value - 1) * pageSize.value;
    const endIndex = startIndex + pageSize.value;
    return items.value.slice(startIndex, endIndex);
  });

  // Page numbers to display (for pagination UI)
  const pageNumbers = computed(() => {
    const pages: number[] = [];
    const total = totalPages.value;
    const current = currentPage.value;
    const max = maxPageNumbers.value;

    __TOKEN_36__ (total <= max) {
      // Show all pages
      __TOKEN_37__ (let i = 1; i <= total; i++) {
        pages.push(i);
      }
    } else {
      // Calculate range around current page
      const half = Math.floor(max / 2);
      let start = Math.max(1, current - half);
      let end = Math.min(total, current + half);

      // Adjust if near start/end
      __TOKEN_43__ (end - start + 1 < max) {
        __TOKEN_44__ (start === 1) {
          end = Math.min(total, start + max - 1);
        } else __TOKEN_46__ (end === total) {
          start = Math.max(1, end - max + 1);
        }
      }

      __TOKEN_47__ (let i = start; i <= end; i++) {
        pages.push(i);
      }

      // Add ellipsis markers
      __TOKEN_49__ (start > 1) {
        pages.unshift(-1); // Ellipsis marker
        pages.unshift(1);
      }
      __TOKEN_50__ (end < total) {
        pages.push(-1); // Ellipsis marker
        pages.push(total);
      }
    }

    return pages;
  });

  // Navigation methods
  const goToPage = (page: number) => {
    __TOKEN_53__ (page < 1 || page > totalPages.value) return;
    currentPage.value = page;
  };

  const nextPage = () => {
    __TOKEN_56__ (currentPage.value < totalPages.value) {
      currentPage.value++;
    }
  };

  const prevPage = () => {
    __TOKEN_58__ (currentPage.value > 1) {
      currentPage.value--;
    }
  };

  const firstPage = () => {
    currentPage.value = 1;
  };

  const lastPage = () => {
    currentPage.value = totalPages.value;
  };

  const setPageSize = (size: number) => {
    pageSize.value = size;
    // Reset to first page when changing page size
    currentPage.value = 1;
  };

  return {
    currentPage,
    pageSize,
    totalItems,
    totalPages,
    paginatedItems,
    pageNumbers,
    goToPage,
    nextPage,
    prevPage,
    firstPage,
    lastPage,
    setPageSize
  };
}

// Usage example
// const items = ref(Array.from({ length: 100 }, (_, i) => ({ id: i + 1 })));
// const { paginatedItems, currentPage, goToPage, nextPage } = usePagination(items);