Skip to content

Commit aec8b2d

Browse files
committed
fix(menu): respect ion-app dir attribute for menu animation side
Use isRTL with nearest ancestor dir lookup, fix sideChanged call site, and add unit/e2e tests for issue #30226.
1 parent 044f358 commit aec8b2d

7 files changed

Lines changed: 75 additions & 8 deletions

core/src/components/menu/menu.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ export class Menu implements ComponentInterface, MenuI {
150150

151151
@Watch('side')
152152
protected sideChanged() {
153-
this.isEndSide = isEnd(this.side);
153+
this.isEndSide = isEnd(this.side, this.el);
154154
/**
155155
* Menu direction animation is calculated based on the document direction.
156156
* If the document direction changes, we need to create a new animation.
@@ -499,7 +499,7 @@ export class Menu implements ComponentInterface, MenuI {
499499
* Menu direction animation is calculated based on the document direction.
500500
* If the document direction changes, we need to create a new animation.
501501
*/
502-
const isEndSide = isEnd(this.side);
502+
const isEndSide = isEnd(this.side, this.el);
503503
if (width === this.width && this.animation !== undefined && isEndSide === this.isEndSide) {
504504
return;
505505
}

core/src/components/menu/test/basic/menu.e2e.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,30 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
137137
});
138138
await ionDidClose.next();
139139
});
140+
141+
test('should render on the correct side when ion-app direction is rtl', async ({ page }) => {
142+
test.info().annotations.push({
143+
type: 'issue',
144+
description: 'https://github.com/ionic-team/ionic-framework/issues/30226',
145+
});
146+
147+
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
148+
const ionDidClose = await page.spyOnEvent('ionDidClose');
149+
150+
await page.evaluate(() => {
151+
document.dir = 'ltr';
152+
document.querySelector('ion-app')?.setAttribute('dir', 'rtl');
153+
});
154+
await page.click('#open-start');
155+
await ionDidOpen.next();
156+
157+
await expect(page).toHaveScreenshot(screenshot(`menu-basic-ion-app-dir-rtl`));
158+
159+
await page.locator('[menu-id="start-menu"]').evaluate(async (el: HTMLIonMenuElement) => {
160+
await el.close();
161+
});
162+
await ionDidClose.next();
163+
});
140164
});
141165
});
142166

19.3 KB
Loading
32.1 KB
Loading
24.4 KB
Loading

core/src/utils/helpers.spec.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,44 @@
1-
import { inheritAriaAttributes } from './helpers';
1+
import { inheritAriaAttributes, isEndSide } from './helpers';
2+
3+
describe('isEndSide', () => {
4+
afterEach(() => {
5+
document.dir = 'ltr';
6+
});
7+
8+
it('should use document direction when no host element is provided', () => {
9+
document.dir = 'rtl';
10+
expect(isEndSide('start')).toBe(true);
11+
expect(isEndSide('end')).toBe(false);
12+
});
13+
14+
it('should use the nearest ancestor dir attribute', () => {
15+
document.dir = 'ltr';
16+
17+
const app = document.createElement('ion-app');
18+
app.setAttribute('dir', 'rtl');
19+
20+
const menu = document.createElement('ion-menu');
21+
app.appendChild(menu);
22+
document.body.appendChild(app);
23+
24+
expect(isEndSide('start', menu)).toBe(true);
25+
expect(isEndSide('end', menu)).toBe(false);
26+
27+
document.body.removeChild(app);
28+
});
29+
30+
it('should fall back to document direction when no ancestor has dir', () => {
31+
document.dir = 'rtl';
32+
33+
const menu = document.createElement('ion-menu');
34+
document.body.appendChild(menu);
35+
36+
expect(isEndSide('start', menu)).toBe(true);
37+
expect(isEndSide('end', menu)).toBe(false);
38+
39+
document.body.removeChild(menu);
40+
});
41+
});
242

343
describe('inheritAriaAttributes', () => {
444
it('should inherit aria attributes', () => {

core/src/utils/helpers.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { EventEmitter } from '@stencil/core';
22
import { printIonError } from '@utils/logging';
3+
import { isRTL } from '@utils/rtl';
34

45
import type { Side } from '../components/menu/menu-interface';
56

@@ -319,15 +320,17 @@ export const pointerCoord = (ev: any): { x: number; y: number } => {
319320
* Given a side, return if it should be on the end
320321
* based on the value of dir
321322
* @param side the side
322-
* @param isRTL whether the application dir is rtl
323+
* @param hostElement the host element used to resolve the nearest ancestor `dir`
323324
*/
324-
export const isEndSide = (side: Side): boolean => {
325-
const isRTL = document.dir === 'rtl';
325+
export const isEndSide = (side: Side, hostElement?: Element): boolean => {
326+
const dirHost = hostElement?.closest('[dir]') as HTMLElement | undefined;
327+
const rtl = isRTL(dirHost);
328+
326329
switch (side) {
327330
case 'start':
328-
return isRTL;
331+
return rtl;
329332
case 'end':
330-
return !isRTL;
333+
return !rtl;
331334
default:
332335
throw new Error(`"${side}" is not a valid value for [side]. Use "start" or "end" instead.`);
333336
}

0 commit comments

Comments
 (0)