import Elementary /// The page links under a card grid; renders nothing for a single page. struct Pagination: HTML { // MARK: Properties /// The page shown, counted from one. let current: Int /// The path of a given page. let path: @Sendable (Int) -> String /// The number of pages. let total: Int // MARK: Initializers /// Creates the page links. /// - Parameters: /// - current: the page shown, counted from one. /// - total: the number of pages. /// - path: the path of a given page. init( current: Int, total: Int, path: @escaping @Sendable (Int) -> String ) { self.current = current self.path = path self.total = total } // MARK: Computed var body: some HTML { if total > 1 { nav( .class("pagination"), .custom( name: "aria-label", value: "Pagination" ) ) { for page in 1...total { if page == current { a( .class("pagination__page"), .href(path(page)), .custom( name: "aria-current", value: "page" ) ) { "\(page)" } } else { a( .class("pagination__page"), .href(path(page)) ) { "\(page)" } } } if current < total { a( .class("pagination__next"), .href(path(current + 1)) ) { "Next" } } } } } } // MARK: - Extensions extension Pagination { // MARK: Methods /// Cuts the items of one page out of a list, clamping the page to the list's range. /// - Parameters: /// - items: the whole list. /// - page: the page asked for, counted from one. /// - size: the number of items on a page. /// - Returns: the page's items, the page actually shown, and the number of pages. static func slice( _ items: [Item], page: Int, size: Int ) -> (items: [Item], current: Int, total: Int) { let total = max((items.count + size - 1) / size, 1) let current = min(max(page, 1), total) return ( Array(items.dropFirst((current - 1) * size).prefix(size)), current, total ) } }