Skip to content

Commit e10f49c

Browse files
authored
fix(accordion): prevent opening of readonly accordion using keyboard (#28865)
Issue number: resolves #28344 --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> When an Accordion is inside an Accordion Group, and the Accordion Group is enabled and not readonly but the Accordion is disabled and/or readonly, it is possible to navigate to the accordion and open it using the keyboard. This should not be allowed, just like opening it on click is not allowed. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - A disabled Accordion inside an Accordion Group may not be opened via the keyboard. - A readonly Accordion inside an Accordion Group may not be opened via the keyboard. ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change: 1. Describe the impact and migration path for existing applications below. 2. Update the BREAKING.md file with the breaking change. 3. Add "BREAKING CHANGE: [...]" to the commit description when merging. See https://github.com/ionic-team/ionic-framework/blob/main/.github/CONTRIBUTING.md#footer for more information. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. -->
1 parent e41a1a1 commit e10f49c

File tree

6 files changed

+460
-50
lines changed

6 files changed

+460
-50
lines changed

core/src/components/accordion/accordion.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,10 @@ export class Accordion implements ComponentInterface {
382382
};
383383

384384
private toggleExpanded() {
385-
const { accordionGroupEl, value, state } = this;
385+
const { accordionGroupEl, disabled, readonly, value, state } = this;
386+
387+
if (disabled || readonly) return;
388+
386389
if (accordionGroupEl) {
387390
/**
388391
* Because the accordion group may or may

core/src/components/accordion/test/accordion.e2e.ts

Lines changed: 0 additions & 49 deletions
This file was deleted.
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { expect } from '@playwright/test';
2+
import { configs, test } from '@utils/test/playwright';
3+
4+
// NOTE: these tests cannot be re-written as spec tests because the `getAccordions` method in accordion-group.tsx uses a `:scope` selector
5+
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ config, title }) => {
6+
test.describe(title('accordion: disabled'), () => {
7+
test('should properly set disabled on child accordions', async ({ page }) => {
8+
await page.setContent(
9+
`
10+
<ion-accordion-group animated="false">
11+
<ion-accordion>
12+
<ion-item slot="header">Label</ion-item>
13+
<div slot="content">Content</div>
14+
</ion-accordion>
15+
</ion-accordion-group>
16+
`,
17+
config
18+
);
19+
20+
const accordionGroup = page.locator('ion-accordion-group');
21+
const accordion = page.locator('ion-accordion');
22+
23+
await expect(accordion).toHaveJSProperty('disabled', false);
24+
25+
await accordionGroup.evaluate((el: HTMLIonAccordionGroupElement) => {
26+
el.disabled = true;
27+
});
28+
29+
await page.waitForChanges();
30+
31+
await expect(accordion).toHaveJSProperty('disabled', true);
32+
});
33+
34+
test('should not open accordion on click when group is disabled', async ({ page }) => {
35+
await page.setContent(
36+
`
37+
<ion-accordion-group animated="false" disabled>
38+
<ion-accordion>
39+
<ion-item slot="header">Label</ion-item>
40+
<div slot="content">Content</div>
41+
</ion-accordion>
42+
</ion-accordion-group>
43+
`,
44+
config
45+
);
46+
47+
const accordion = page.locator('ion-accordion');
48+
49+
await expect(accordion).toHaveClass(/accordion-collapsed/);
50+
51+
accordion.click();
52+
await page.waitForChanges();
53+
54+
await expect(accordion).toHaveClass(/accordion-collapsed/);
55+
});
56+
57+
test('should not open accordion on click when accordion is disabled', async ({ page }) => {
58+
await page.setContent(
59+
`
60+
<ion-accordion-group animated="false">
61+
<ion-accordion disabled>
62+
<ion-item slot="header">Label</ion-item>
63+
<div slot="content">Content</div>
64+
</ion-accordion>
65+
</ion-accordion-group>
66+
`,
67+
config
68+
);
69+
70+
const accordion = page.locator('ion-accordion');
71+
72+
await expect(accordion).toHaveClass(/accordion-collapsed/);
73+
74+
accordion.click();
75+
await page.waitForChanges();
76+
77+
await expect(accordion).toHaveClass(/accordion-collapsed/);
78+
});
79+
80+
test('should not open accordion via keyboard navigation when group is disabled', async ({ page, browserName }) => {
81+
await page.setContent(
82+
`
83+
<ion-accordion-group animated="false" disabled>
84+
<ion-accordion>
85+
<ion-item slot="header">Label</ion-item>
86+
<div slot="content">Content</div>
87+
</ion-accordion>
88+
</ion-accordion-group>
89+
`,
90+
config
91+
);
92+
93+
const accordion = page.locator('ion-accordion');
94+
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
95+
96+
await expect(accordion).toHaveClass(/accordion-collapsed/);
97+
98+
await page.keyboard.press(tabKey);
99+
await page.waitForChanges();
100+
101+
await page.keyboard.press('Enter');
102+
await page.waitForChanges();
103+
104+
await expect(accordion).toHaveClass(/accordion-collapsed/);
105+
});
106+
107+
test('should not open accordion via keyboard navigation when accordion is disabled', async ({
108+
page,
109+
browserName,
110+
}) => {
111+
await page.setContent(
112+
`
113+
<ion-accordion-group animated="false">
114+
<ion-accordion disabled>
115+
<ion-item slot="header">Label</ion-item>
116+
<div slot="content">Content</div>
117+
</ion-accordion>
118+
</ion-accordion-group>
119+
`,
120+
config
121+
);
122+
123+
const accordion = page.locator('ion-accordion');
124+
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
125+
126+
await expect(accordion).toHaveClass(/accordion-collapsed/);
127+
128+
await page.keyboard.press(tabKey);
129+
await page.waitForChanges();
130+
131+
await page.keyboard.press('Enter');
132+
await page.waitForChanges();
133+
134+
await expect(accordion).toHaveClass(/accordion-collapsed/);
135+
});
136+
});
137+
});
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<!DOCTYPE html>
2+
<html lang="en" dir="ltr">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>Accordion - Disabled</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
7+
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
8+
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
9+
<script src="../../../../../scripts/testing/scripts.js"></script>
10+
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
11+
<style>
12+
.grid {
13+
display: grid;
14+
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
15+
grid-row-gap: 20px;
16+
grid-column-gap: 20px;
17+
}
18+
</style>
19+
</head>
20+
<body>
21+
<ion-app>
22+
<ion-header translucent="true">
23+
<ion-toolbar>
24+
<ion-title>Accordion - Disabled</ion-title>
25+
</ion-toolbar>
26+
</ion-header>
27+
<ion-content fullscreen="true" color="light">
28+
<ion-header collapse="condense">
29+
<ion-toolbar color="light">
30+
<ion-title size="large">Accordion - Disabled</ion-title>
31+
</ion-toolbar>
32+
</ion-header>
33+
34+
<div class="grid ion-padding">
35+
<div class="grid-item">
36+
<ion-accordion-group>
37+
<ion-accordion value="first">
38+
<ion-item slot="header" color="light">
39+
<ion-label>First Accordion</ion-label>
40+
</ion-item>
41+
<div class="ion-padding" slot="content">First Content</div>
42+
</ion-accordion>
43+
<ion-accordion value="second" disabled="true">
44+
<ion-item slot="header" color="light">
45+
<ion-label>Second Accordion (Disabled)</ion-label>
46+
</ion-item>
47+
<div class="ion-padding" slot="content">Second Content</div>
48+
</ion-accordion>
49+
<ion-accordion value="third">
50+
<ion-item slot="header" color="light">
51+
<ion-label>Third Accordion</ion-label>
52+
</ion-item>
53+
<div class="ion-padding" slot="content">Third Content</div>
54+
</ion-accordion>
55+
</ion-accordion-group>
56+
</div>
57+
<div class="grid-item">
58+
<ion-accordion-group disabled="true">
59+
<ion-accordion value="first">
60+
<ion-item slot="header" color="light">
61+
<ion-label>First Accordion in Disabled Group</ion-label>
62+
</ion-item>
63+
<div class="ion-padding" slot="content">First Content</div>
64+
</ion-accordion>
65+
<ion-accordion value="second">
66+
<ion-item slot="header" color="light">
67+
<ion-label>Second Accordion in Disabled Group</ion-label>
68+
</ion-item>
69+
<div class="ion-padding" slot="content">Second Content</div>
70+
</ion-accordion>
71+
<ion-accordion value="third">
72+
<ion-item slot="header" color="light">
73+
<ion-label>Third Accordion in Disabled Group</ion-label>
74+
</ion-item>
75+
<div class="ion-padding" slot="content">Third Content</div>
76+
</ion-accordion>
77+
</ion-accordion-group>
78+
</div>
79+
<div class="grid-item">
80+
<ion-accordion value="second" disabled="true">
81+
<ion-item slot="header" color="light">
82+
<ion-label>Accordion Without Group (Disabled)</ion-label>
83+
</ion-item>
84+
<div class="ion-padding" slot="content">Second Content</div>
85+
</ion-accordion>
86+
</div>
87+
</div>
88+
</ion-content>
89+
</ion-app>
90+
</body>
91+
</html>

0 commit comments

Comments
 (0)