Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions assets/components/pagination-connect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { connect, type Api } from "@zag-js/pagination";
import type { normalizeProps } from "@zag-js/vanilla";

type PaginationService = Parameters<typeof connect>[0];
type Normalize = typeof normalizeProps;

export function adjustDeadLinkTriggerProps(
props: Record<string, unknown>
): Record<string, unknown> {
if (props.href != null && props.href !== "") return props;
if (props.type === "button") return props;
return { ...props, "aria-label": undefined };
}

export function corexPaginationConnect(service: PaginationService, normalize: Normalize): Api {
const api = connect(service, normalize);

return {
...api,
getPrevTriggerProps() {
return adjustDeadLinkTriggerProps(api.getPrevTriggerProps() as Record<string, unknown>);
},
getNextTriggerProps() {
return adjustDeadLinkTriggerProps(api.getNextTriggerProps() as Record<string, unknown>);
},
};
}
5 changes: 3 additions & 2 deletions assets/components/pagination.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { connect, machine, type Api, type Props } from "@zag-js/pagination";
import { machine, type Api, type Props } from "@zag-js/pagination";
import type { IntlTranslations } from "@zag-js/pagination";
import { VanillaMachine } from "@zag-js/vanilla";
import { Component } from "../lib/core";
import { corexPaginationConnect } from "./pagination-connect";
import { isAllowedRedirectDestination } from "../lib/redirect";
import { cloneTemplateChildren, getString } from "../lib/util";

Expand All @@ -12,7 +13,7 @@ export class Pagination extends Component<Props, Api> {
}

initApi(): Api {
return this.zagConnect(connect);
return this.zagConnect(corexPaginationConnect);
}

render(): void {
Expand Down
7 changes: 6 additions & 1 deletion assets/test/component/components-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ const loaders = import.meta.glob<Record<string, unknown>>("../../components/*.ts

describe("component modules", () => {
for (const [path, load] of Object.entries(loaders)) {
if (path.includes(".test.ts") || path.includes("components-contract")) continue;
if (
path.includes(".test.ts") ||
path.includes("components-contract") ||
path.endsWith("-connect.ts")
)
continue;

it(`${path.replace("../../components/", "")} exports a Component subclass`, async () => {
const mod = await load();
Expand Down
138 changes: 138 additions & 0 deletions assets/test/component/pagination.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest";
import { el } from "../helpers/dom";
import { adjustDeadLinkTriggerProps } from "../../components/pagination-connect";
import {
applyPhoenixLinkAttrs,
applyPhoenixLinkAttrsToNavigableParts,
Expand All @@ -10,6 +11,50 @@ import {
} from "../../components/pagination";
import { paginationTree } from "../helpers/component-smoke";

function linkPaginationHost(id = "pager") {
const root = document.createElement("div");
root.id = id;
root.dataset.type = "link";
root.dataset.to = "/items";
root.dataset.redirect = "patch";
root.dataset.pageParam = "page";
root.dataset.pageSizeParam = "page_size";
root.dataset.translation = JSON.stringify({
prevTriggerLabel: "Previous",
nextTriggerLabel: "Next",
});

const nav = document.createElement("nav");
nav.dataset.scope = "pagination";
nav.dataset.part = "root";
nav.id = `pagination:${id}`;

const ul = document.createElement("ul");

const prevLi = document.createElement("li");
const prev = document.createElement("a");
prev.dataset.scope = "pagination";
prev.dataset.part = "prev-trigger";
prev.dataset.paginationPart = "prev";
prev.id = `pagination:${id}:prev`;
prevLi.appendChild(prev);

const nextLi = document.createElement("li");
nextLi.setAttribute("data-pagination-part", "next");
const next = document.createElement("a");
next.dataset.scope = "pagination";
next.dataset.part = "next-trigger";
next.dataset.paginationPart = "next";
next.id = `pagination:${id}:next`;
nextLi.appendChild(next);

ul.append(prevLi, nextLi);
nav.appendChild(ul);
root.appendChild(nav);

return root;
}

describe("parsePaginationTranslations", () => {
it("parses JSON translation map", () => {
const node = document.createElement("div");
Expand Down Expand Up @@ -72,6 +117,99 @@ describe("applyPhoenixLinkAttrsToNavigableParts", () => {
});
});

describe("adjustDeadLinkTriggerProps", () => {
it("strips aria-label on dead link triggers without href", () => {
const adjusted = adjustDeadLinkTriggerProps({
"aria-label": "Previous",
"data-disabled": "",
});
expect(adjusted["aria-label"]).toBeUndefined();
});

it("keeps aria-label when href is set", () => {
const adjusted = adjustDeadLinkTriggerProps({
"aria-label": "Previous",
href: "/items?page=1&page_size=10",
});
expect(adjusted["aria-label"]).toBe("Previous");
});

it("keeps aria-label on disabled button triggers", () => {
const adjusted = adjustDeadLinkTriggerProps({
"aria-label": "Previous",
type: "button",
disabled: true,
"data-disabled": "",
});
expect(adjusted["aria-label"]).toBe("Previous");
});
});

describe("Pagination link trigger aria-label", () => {
it("omits aria-label on dead prev link on page 1", () => {
const host = linkPaginationHost();
const pagination = new Pagination(host, {
id: "pager",
count: 50,
defaultPage: 1,
pageSize: 10,
type: "link",
getPageUrl: ({ page, pageSize }) => `/items?page=${page}&page_size=${pageSize}`,
});
pagination.init();

const prev = host.querySelector<HTMLElement>('[data-part="prev-trigger"]');
expect(prev?.hasAttribute("data-disabled")).toBe(true);
expect(prev?.hasAttribute("href")).toBe(false);
expect(prev?.hasAttribute("aria-label")).toBe(false);

pagination.destroy();
});

it("keeps aria-label on navigable prev link", () => {
const host = linkPaginationHost();
const pagination = new Pagination(host, {
id: "pager",
count: 50,
defaultPage: 2,
pageSize: 10,
type: "link",
getPageUrl: ({ page, pageSize }) => `/items?page=${page}&page_size=${pageSize}`,
translations: {
prevTriggerLabel: "Previous",
nextTriggerLabel: "Next",
},
});
pagination.init();

const prev = host.querySelector<HTMLElement>('[data-part="prev-trigger"]');
expect(prev?.getAttribute("href")).toContain("page=1");
expect(prev?.hasAttribute("aria-label")).toBe(true);

pagination.destroy();
});

it("omits aria-label on dead next link on last page", () => {
const host = linkPaginationHost();
const pagination = new Pagination(host, {
id: "pager",
count: 50,
defaultPage: 5,
pageSize: 10,
type: "link",
getPageUrl: ({ page, pageSize }) => `/items?page=${page}&page_size=${pageSize}`,
});
pagination.init();

const next = host.querySelector<HTMLElement>('[data-part="next-trigger"]');
expect(next?.hasAttribute("data-disabled")).toBe(true);
expect(next?.hasAttribute("href")).toBe(false);
expect(next?.hasAttribute("aria-label")).toBe(false);

pagination.destroy();
});
});

describe("Pagination ellipsis template", () => {
it("clones ellipsis slot content instead of using innerHTML", () => {
const root = document.createElement("div");
Expand Down
18 changes: 17 additions & 1 deletion priv/static/corex.js
Original file line number Diff line number Diff line change
Expand Up @@ -31815,6 +31815,22 @@ ${err}`);
}
};
}
function adjustDeadLinkTriggerProps(props) {
if (props.href != null && props.href !== "") return props;
if (props.type === "button") return props;
return __spreadProps(__spreadValues({}, props), { "aria-label": void 0 });
}
function corexPaginationConnect(service, normalize) {
const api = connect19(service, normalize);
return __spreadProps(__spreadValues({}, api), {
getPrevTriggerProps() {
return adjustDeadLinkTriggerProps(api.getPrevTriggerProps());
},
getNextTriggerProps() {
return adjustDeadLinkTriggerProps(api.getNextTriggerProps());
}
});
}
function uniquePaginationTranslations(el, translations) {
var _a4;
const label = ((_a4 = translations == null ? void 0 : translations.rootLabel) == null ? void 0 : _a4.trim()) || "Pagination";
Expand Down Expand Up @@ -32190,7 +32206,7 @@ ${err}`);
return new VanillaMachine(machine19, props);
}
initApi() {
return this.zagConnect(connect19);
return this.zagConnect(corexPaginationConnect);
}
render() {
const rootEl = this.el.querySelector(
Expand Down
18 changes: 9 additions & 9 deletions priv/static/corex.min.js

Large diffs are not rendered by default.

21 changes: 20 additions & 1 deletion priv/static/pagination.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -392,14 +392,33 @@ var machine = createMachine({
});
var clampPage = (page, totalPages) => Math.min(Math.max(page, 1), totalPages);

// components/pagination-connect.ts
function adjustDeadLinkTriggerProps(props) {
if (props.href != null && props.href !== "") return props;
if (props.type === "button") return props;
return { ...props, "aria-label": void 0 };
}
function corexPaginationConnect(service, normalize) {
const api = connect(service, normalize);
return {
...api,
getPrevTriggerProps() {
return adjustDeadLinkTriggerProps(api.getPrevTriggerProps());
},
getNextTriggerProps() {
return adjustDeadLinkTriggerProps(api.getNextTriggerProps());
}
};
}

// components/pagination.ts
var Pagination = class extends Component {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
initMachine(props) {
return new VanillaMachine(machine, props);
}
initApi() {
return this.zagConnect(connect);
return this.zagConnect(corexPaginationConnect);
}
render() {
const rootEl = this.el.querySelector(
Expand Down
16 changes: 16 additions & 0 deletions test/components/pagination_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,22 @@ defmodule Corex.PaginationTest do
assert attrs["data-disabled"] == ""
end

test "next_trigger disabled link omits aria-label" do
attrs =
Connect.next_trigger(%NextTrigger{
id: "p1",
dir: "ltr",
disabled: true,
aria_label: "Next",
href: nil,
redirect: "patch",
tag: "link"
})

refute Map.has_key?(attrs, "aria-label")
assert attrs["data-disabled"] == ""
end

test "root aria-label is unique per id" do
attrs =
Connect.root(%Corex.Pagination.Anatomy.Root{
Expand Down
Loading