Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .changeset/accordion-locked-aria-disabled.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@zag-js/accordion": patch
---

- Fixed issue where an expanded item that cannot be collapsed was not marked `aria-disabled`.
21 changes: 20 additions & 1 deletion e2e/accordion.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ test.describe("accordion", () => {

test("then clicking the same trigger again: should not close the content", async () => {
await I.clickTrigger("home")
await I.clickTrigger("home")
// the expanded trigger has aria-disabled, which playwright treats as not clickable
await I.clickTrigger("home", { force: true })
await I.seeContent("home")
})

Expand All @@ -59,6 +60,24 @@ test.describe("accordion", () => {
await I.seeContent("about")
await I.dontSeeContent("home")
})

test("expanded trigger that cannot collapse has aria-disabled", async () => {
await I.clickTrigger("home")
await I.seeTriggerIsLocked("home")
await I.dontSeeTriggerIsLocked("about")

await I.clickTrigger("about")
await I.seeTriggerIsLocked("about")
await I.dontSeeTriggerIsLocked("home")
})

test("[collapsible=true] expanded trigger has no aria-disabled", async () => {
await I.controls.bool("collapsible")

await I.clickTrigger("home")
await I.seeContent("home")
await I.dontSeeTriggerIsLocked("home")
})
})

test.describe("multiple / keyboard", () => {
Expand Down
12 changes: 10 additions & 2 deletions e2e/models/accordion.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ export class AccordionModel extends Model {
await this.getTrigger(id).focus()
}

async clickTrigger(id: string) {
await this.getTrigger(id).click()
async clickTrigger(id: string, options?: { force?: boolean }) {
await this.getTrigger(id).click(options)
}

async seeTriggerIsFocused(id: string) {
Expand All @@ -42,4 +42,12 @@ export class AccordionModel extends Model {
async dontSeeContent(id: string) {
await expect(this.getContent(id)).not.toBeVisible()
}

async seeTriggerIsLocked(id: string) {
await expect(this.getTrigger(id)).toHaveAttribute("aria-disabled", "true")
}

async dontSeeTriggerIsLocked(id: string) {
await expect(this.getTrigger(id)).not.toHaveAttribute("aria-disabled")
}
}
5 changes: 4 additions & 1 deletion packages/machines/accordion/src/accordion.connect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { dataAttr, getEventKey, isSafari } from "@zag-js/dom-query"
import { ariaAttr, dataAttr, getEventKey, isSafari } from "@zag-js/dom-query"
import type { EventKeyMap, NormalizeProps, PropTypes } from "@zag-js/types"
import type { Service } from "@zag-js/core"
import { parts } from "./accordion.anatomy"
Expand Down Expand Up @@ -91,6 +91,8 @@ export function connect<T extends PropTypes>(
getItemTriggerProps(props) {
const { value } = props
const itemState = getItemState(props)
// an expanded item that cannot be collapsed is announced as disabled
const locked = itemState.expanded && !itemState.disabled && !prop("collapsible") && !multiple

return normalize.button({
...parts.itemTrigger.attrs,
Expand All @@ -101,6 +103,7 @@ export function connect<T extends PropTypes>(
"data-controls": dom.getItemContentId(scope, value),
"aria-expanded": itemState.expanded,
disabled: itemState.disabled,
"aria-disabled": ariaAttr(locked),
"data-orientation": prop("orientation"),
"data-state": itemState.expanded ? "open" : "closed",
"data-focus": dataAttr(itemState.focused),
Expand Down
Loading