====== Paginator ======
この実装を Vue3 用に移植してみました。
https://github.com/lokyoung/vuejs-paginate/blob/master/src/components/Paginate.vue
実際にリストに実装しての確認は、まだしていません。。。
===== Code =====
import { onBeforeUpdate, ref, computed } from 'https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.2/vue.esm-browser.js';
const Paginator = {
  template: `
  
    ul.paginator a { cursor: pointer; }
  
  
  
  `,
  props: {
    value:              { type: Number },
    pageCount:          { type: Number,   required: true },
    forcePage:          { type: Number },
    clickHandler:       { type: Function, default: () => {} },
    pageRange:          { type: Number,   default: 3 },
    marginPages:        { type: Number,   default: 1 },
    prevText:           { type: String,   default: '<' },
    nextText:           { type: String,   default: '>' },
    breakViewText:      { type: String,   default: '…'    },
    containerClass:     { type: String,   default: 'pagination pagination-sm pull-right' },
    pageClass:          { type: String },
    pageLinkClass:      { type: String },
    prevClass:          { type: String },
    prevLinkClass:      { type: String },
    nextClass:          { type: String },
    nextLinkClass:      { type: String },
    breakViewClass:     { type: String },
    breakViewLinkClass: { type: String },
    activeClass:        { type: String,  default: 'active' },
    disabledClass:      { type: String,  default: 'disabled' },
    noLiSurround:       { type: Boolean, default: false },
    firstLastButton:    { type: Boolean, default: true },
    firstButtonText:    { type: String,  default: '<<' },
    lastButtonText:     { type: String,  default: '>>' },
    hidePrevNext:       { type: Boolean, default: false }
  },
  setup(props, context) {
    const innerValue = ref(1)
    const selected = computed({
      get: function() {
        return props.value || innerValue.value
      },
      set: function(newValue) {
        innerValue.value = newValue
      }
    })
    const pages = computed(function() {
      let items = {}
      if (props.pageCount <= props.pageRange) {
        for (let index = 0; index < props.pageCount; index++) {
          let page = {
            index: index,
            content: index + 1,
            selected: index === (selected - 1)
          }
          items[index] = page
        }
      } else {
        const halfPageRange = Math.floor(props.pageRange / 2)
        let setPageItem = index => {
          let page = {
            index: index,
            content: index + 1,
            selected: index === (selected - 1)
          }
          items[index] = page
        }
        let setBreakView = index => {
          let breakView = {
            disabled: true,
            breakView: true
          }
          items[index] = breakView
        }
        // 1st - loop thru low end of margin pages
        for (let i = 0; i < props.marginPages; i++) {
          setPageItem(i);
        }
        // 2nd - loop thru selected range
        let selectedRangeLow = 0;
        if (selected.value - halfPageRange > 0) {
          selectedRangeLow = selected.value - 1 - halfPageRange;
        }
        let selectedRangeHigh = selectedRangeLow + props.pageRange - 1;
        if (selectedRangeHigh >= props.pageCount) {
          selectedRangeHigh = props.pageCount - 1;
          selectedRangeLow = selectedRangeHigh - props.pageRange + 1;
        }
        for (let i = selectedRangeLow; i <= selectedRangeHigh && i <= props.pageCount - 1; i++) {
          setPageItem(i);
        }
        // Check if there is breakView in the left of selected range
        if (selectedRangeLow > props.marginPages) {
          setBreakView(selectedRangeLow - 1)
        }
        // Check if there is breakView in the right of selected range
        if (selectedRangeHigh + 1 < props.pageCount - props.marginPages) {
          setBreakView(selectedRangeHigh + 1)
        }
        // 3rd - loop thru high end of margin pages
        for (let i = props.pageCount - 1; i >= props.pageCount - props.marginPages; i--) {
          setPageItem(i);
        }
      }
      return items
    })
    onBeforeUpdate(() => {
      if (props.forcePage === undefined) return
      if (props.forcePage !== selected.value) {
        selected.value = props.forcePage
      }
    })
    function handlePageSelected(value) {
      if (selected.value === value) return
      innerValue.value = value
      context.emit('input', value)
      props.clickHandler(value)
    }
    function prevPage() {
      if (selected.value <= 1) return
      handlePageSelected(selected.value - 1)
    }
    function nextPage() {
      if (selected.value >= props.pageCount) return
      handlePageSelected(selected.value + 1)
    }
    function firstPageSelected() {
      return selected.value === 1
    }
    function lastPageSelected() {
      return (selected.value === props.pageCount) || (props.pageCount === 0)
    }
    function selectFirstPage() {
      if (selected.value <= 1) return
      handlePageSelected(1)
    }
    function selectLastPage() {
      if (selected.value >= props.pageCount) return
      handlePageSelected(props.pageCount)
    }
    return {
      innerValue,
      selected,
      pages,
      handlePageSelected,
      prevPage,
      nextPage,
      firstPageSelected,
      lastPageSelected,
      selectFirstPage,
      selectLastPage
    }
  }
};
export default Paginator;
===== Demo =====