diff --git a/src/components/icon/icon.css b/src/components/icon/icon.css index b276d94e1..3a17e64f1 100644 --- a/src/components/icon/icon.css +++ b/src/components/icon/icon.css @@ -33,15 +33,19 @@ svg { width: 100%; } - /* Icon RTL * ----------------------------------------------------------- */ -:host(.flip-rtl) .icon-inner { +/* :host-context is supported in chromium; :dir is supported in safari & firefox */ +:host(.flip-rtl):host-context([dir='rtl']) .icon-inner { transform: scaleX(-1); } - +@supports selector(:dir(rtl)) { + :host(.flip-rtl:dir(rtl)) .icon-inner { + transform: scaleX(-1); + } +} /* Icon Sizes * ----------------------------------------------------------- @@ -51,11 +55,10 @@ svg { font-size: 18px !important; } -:host(.icon-large){ +:host(.icon-large) { font-size: 32px !important; } - /* Icon Colors * ----------------------------------------------------------- */ diff --git a/src/components/icon/icon.tsx b/src/components/icon/icon.tsx index ddf954d70..540825882 100755 --- a/src/components/icon/icon.tsx +++ b/src/components/icon/icon.tsx @@ -1,6 +1,6 @@ import { Build, Component, Element, Host, Prop, State, Watch, h } from '@stencil/core'; import { getSvgContent, ioniconContent } from './request'; -import { getName, getUrl, inheritAttributes, isRTL } from './utils'; +import { getName, getUrl, inheritAttributes } from './utils'; @Component({ tag: 'ion-icon', @@ -100,7 +100,6 @@ export class Icon { this.io = undefined; } } - private waitUntilVisible(el: HTMLElement, rootMargin: string, cb: () => void) { if (Build.isBrowser && this.lazy && typeof window !== 'undefined' && (window as any).IntersectionObserver) { const io = (this.io = new (window as any).IntersectionObserver( @@ -146,11 +145,14 @@ export class Icon { } render() { - const { iconName, el, inheritedAttributes } = this; + const { flipRtl, iconName, inheritedAttributes } = this; const mode = this.mode || 'md'; - const flipRtl = - this.flipRtl || - (iconName && (iconName.indexOf('arrow') > -1 || iconName.indexOf('chevron') > -1) && this.flipRtl !== false); + // we have designated that arrows & chevrons should automatically flip (unless flip-rtl is set to false) because "back" is left in ltr and right in rtl, and "forward" is the opposite + const shouldAutoFlip = iconName + ? (iconName.includes('arrow') || iconName.includes('chevron')) && flipRtl !== false + : false; + // if shouldBeFlippable is true, the icon should change direction when `dir` changes + const shouldBeFlippable = flipRtl || shouldAutoFlip; return ( diff --git a/src/components/icon/test/icon.e2e.ts b/src/components/icon/test/icon.e2e.ts index 9f39ba950..d7c04e04a 100644 --- a/src/components/icon/test/icon.e2e.ts +++ b/src/components/icon/test/icon.e2e.ts @@ -4,10 +4,67 @@ import { test } from '@utils/test/playwright'; test.describe('icon: basic', () => { test('should not have visual regressions', async ({ page }) => { await page.goto(`/`); - + // Wait for all SVGs to be lazily loaded before taking screenshots await page.waitForLoadState('networkidle'); expect(await page.screenshot({ fullPage: true })).toMatchSnapshot(`icon-diff.png`); }); -}); \ No newline at end of file + + test('some icons should flip when rtl', async ({ page }) => { + await page.goto(`/`); + + const autoflip = page.locator('.auto-flip-chevrons [name=chevron-forward] .icon-inner'); + const unflip = page.locator('.un-flip-chevrons [name=chevron-forward] .icon-inner'); + await expect(autoflip).not.toHaveCSS('transform', /matrix\(-1/); + await expect(unflip).not.toHaveCSS('transform', /matrix\(-1/); + + await page.evaluate(() => { + document.dir = 'rtl'; + }); + + await expect(autoflip).toHaveCSS('transform', /matrix\(-1/); + await expect(unflip).not.toHaveCSS('transform', /matrix\(-1/); + + // Wait for all SVGs to be lazily loaded before taking screenshots + await page.waitForLoadState('networkidle'); + + const rtlTests = page.locator('#rtl-tests'); + await expect(rtlTests).toHaveScreenshot(`icon-rtl-diff.png`); + }); + + test('arrows should flip if dir changes on the element', async ({ page }) => { + await page.goto(`/`); + + const autoflip = page.locator('.auto-flip-chevrons [name=chevron-forward] .icon-inner'); + const unflip = page.locator('.un-flip-chevrons [name=chevron-forward] .icon-inner'); + await expect(autoflip).not.toHaveCSS('transform', /matrix\(-1/); + await expect(unflip).not.toHaveCSS('transform', /matrix\(-1/); + + const autoflipEl = await page.$('.auto-flip-chevrons [name=chevron-forward]'); + const unflipEl = await page.$('.un-flip-chevrons [name=chevron-forward]'); + await autoflipEl!.evaluate((node) => node.setAttribute('dir', 'rtl')); + await unflipEl!.evaluate((node) => node.setAttribute('dir', 'rtl')); + + await expect(autoflip).toHaveCSS('transform', /matrix\(-1/); + await expect(unflip).not.toHaveCSS('transform', /matrix\(-1/); + }); + + test('icon should reassess flipping when name changes', async ({ page }) => { + await page.goto(`/`); + + await page.evaluate(() => { + document.dir = 'rtl'; + }); + + const iconLoc = page.locator('.auto-flip-chevrons ion-icon:nth-child(2)'); + await expect(iconLoc).toHaveAttribute('name', 'chevron-forward'); + await expect(iconLoc).toHaveClass(/flip-rtl/); + + const iconEl = await page.$('.auto-flip-chevrons ion-icon:nth-child(2)'); + await iconEl!.evaluate((node) => node.setAttribute('name', 'brush')); + + await expect(iconLoc).toHaveAttribute('name', 'brush'); + await expect(iconLoc).not.toHaveClass(/flip-rtl/); + }); +}); diff --git a/src/components/icon/test/icon.e2e.ts-snapshots/icon-rtl-diff-Mobile-Chrome-linux.png b/src/components/icon/test/icon.e2e.ts-snapshots/icon-rtl-diff-Mobile-Chrome-linux.png new file mode 100644 index 000000000..f2cbc5ec0 Binary files /dev/null and b/src/components/icon/test/icon.e2e.ts-snapshots/icon-rtl-diff-Mobile-Chrome-linux.png differ diff --git a/src/components/icon/test/icon.e2e.ts-snapshots/icon-rtl-diff-Mobile-Firefox-linux.png b/src/components/icon/test/icon.e2e.ts-snapshots/icon-rtl-diff-Mobile-Firefox-linux.png new file mode 100644 index 000000000..e6d7a4943 Binary files /dev/null and b/src/components/icon/test/icon.e2e.ts-snapshots/icon-rtl-diff-Mobile-Firefox-linux.png differ diff --git a/src/components/icon/test/icon.e2e.ts-snapshots/icon-rtl-diff-Mobile-Safari-linux.png b/src/components/icon/test/icon.e2e.ts-snapshots/icon-rtl-diff-Mobile-Safari-linux.png new file mode 100644 index 000000000..83274a176 Binary files /dev/null and b/src/components/icon/test/icon.e2e.ts-snapshots/icon-rtl-diff-Mobile-Safari-linux.png differ diff --git a/src/components/icon/test/icon.spec.ts b/src/components/icon/test/icon.spec.ts index 0339a543c..6c511ad59 100644 --- a/src/components/icon/test/icon.spec.ts +++ b/src/components/icon/test/icon.spec.ts @@ -35,11 +35,11 @@ describe('icon', () => { it('renders custom aria-label', async () => { const { root } = await newSpecPage({ components: [Icon], - html: ``, + html: ``, }); expect(root).toEqualHtml(` - +
@@ -56,7 +56,7 @@ describe('icon', () => { const icon = page.root; expect(icon).toEqualHtml(` - +
diff --git a/src/components/icon/utils.ts b/src/components/icon/utils.ts index 3ffc9dad5..fc62eb731 100644 --- a/src/components/icon/utils.ts +++ b/src/components/icon/utils.ts @@ -140,17 +140,3 @@ export const inheritAttributes = (el: HTMLElement, attributes: string[] = []) => return attributeObject; } - -/** - * Returns `true` if the document or host element - * has a `dir` set to `rtl`. The host value will always - * take priority over the root document value. - */ -export const isRTL = (hostEl?: Pick) => { - if (hostEl) { - if (hostEl.dir !== '') { - return hostEl.dir.toLowerCase() === 'rtl'; - } - } - return document?.dir.toLowerCase() === 'rtl'; -}; \ No newline at end of file diff --git a/src/index.html b/src/index.html index d7291cec6..b6ae618cc 100644 --- a/src/index.html +++ b/src/index.html @@ -86,49 +86,55 @@

Aria

-

RTL

- -

Default: Non-arrows

- - - - - -

Flip: Non-arrows

- - - - - -

Auto Flip: arrows

- - - - - -

Un-flip: arrows

- - - - - -

Auto Flip: chevrons

- - - - - -

Un-flip: chevrons

- - - - - -

Auto Flip, RTL on components

- - - - +
+

RTL

+ +

Default: Non-arrows

+ + + + + +

Flip: Non-arrows

+ + + + + +

Auto Flip: arrows

+ + + + + +

Un-flip: arrows

+ + + + + +

Auto Flip: chevrons

+
+ + + + +
+ +

Un-flip: chevrons

+
+ + + + +
+ +

Auto Flip, RTL on components

+ + + + +

Sanitized (shouldn't show)