Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(item, card): aria-label is reflected to the inner button #26028

Merged
merged 14 commits into from
Oct 10, 2022
Merged
Show file tree
Hide file tree
Changes from 8 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
16 changes: 13 additions & 3 deletions core/src/components/card/card.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Host, Prop, h } from '@stencil/core';
import { Element, Component, Host, Prop, h } from '@stencil/core';

import { getIonMode } from '../../global/ionic-global';
import type { AnimationBuilder, Color, Mode, RouterDirection } from '../../interface';
import type { AnchorInterface, ButtonInterface } from '../../utils/element-interface';
import type { Attributes } from '../../utils/helpers';
import { inheritAttributes } from '../../utils/helpers';
import { createColorClasses, openURL } from '../../utils/theme';

/**
Expand All @@ -20,6 +22,9 @@ import { createColorClasses, openURL } from '../../utils/theme';
shadow: true,
})
export class Card implements ComponentInterface, AnchorInterface, ButtonInterface {
private inheritedAttributes: Attributes = {};

@Element() el!: HTMLElement;
/**
* The color to use from your application's color palette.
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
Expand Down Expand Up @@ -81,6 +86,10 @@ export class Card implements ComponentInterface, AnchorInterface, ButtonInterfac
*/
@Prop() target: string | undefined;

componentWillLoad() {
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
}

private isClickable(): boolean {
return this.href !== undefined || this.button;
}
Expand All @@ -91,11 +100,12 @@ export class Card implements ComponentInterface, AnchorInterface, ButtonInterfac
if (!clickable) {
return [<slot></slot>];
}
const { href, routerAnimation, routerDirection } = this;
const { href, routerAnimation, routerDirection, inheritedAttributes } = this;
const TagType = clickable ? (href === undefined ? 'button' : 'a') : ('div' as any);
const { ['aria-label']: ariaLabel } = inheritedAttributes;
const attrs =
TagType === 'button'
? { type: this.type }
? { type: this.type, ['aria-label']: ariaLabel }
sean-perkins marked this conversation as resolved.
Show resolved Hide resolved
: {
download: this.download,
href: this.href,
Expand Down
17 changes: 17 additions & 0 deletions core/src/components/card/test/aria.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { newSpecPage } from '@stencil/core/testing';

import { Card } from '../card';

describe('card: button', () => {
it('should reflect aria-label to button', async () => {
const page = await newSpecPage({
components: [Card],
html: `<ion-card button="true" aria-label="Test"></ion-card>`,
});

const button = page.body.querySelector('ion-card')!.shadowRoot!.querySelector('button')!;
const ariaLabel = button.getAttribute('aria-label');

expect(ariaLabel).toEqual('Test');
});
});
40 changes: 40 additions & 0 deletions core/src/components/card/test/basic/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Card - Basic</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>

<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Card - Basic</ion-title>
</ion-toolbar>
</ion-header>

<ion-content class="ion-padding">
<ion-card>
<ion-card-header>
<ion-card-subtitle>Card Subtitle</ion-card-subtitle>
<ion-card-title>Card Title</ion-card-title>
</ion-card-header>

<ion-card-content>
Keep close to Nature's heart... and break clear away, once in awhile, and climb a mountain or spend a week
in the woods. Wash your spirit clean.
</ion-card-content>
</ion-card>
</ion-content>
</ion-app>
</body>
</html>
11 changes: 9 additions & 2 deletions core/src/components/item/item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { chevronForward } from 'ionicons/icons';
import { getIonMode } from '../../global/ionic-global';
import type { AnimationBuilder, Color, CssClassMap, RouterDirection, StyleEventDetail } from '../../interface';
import type { AnchorInterface, ButtonInterface } from '../../utils/element-interface';
import { raf } from '../../utils/helpers';
import type { Attributes } from '../../utils/helpers';
import { inheritAttributes, raf } from '../../utils/helpers';
import { printIonError } from '../../utils/logging';
import { createColorClasses, hostContext, openURL } from '../../utils/theme';
import type { InputChangeEventDetail } from '../input/input-interface';
Expand Down Expand Up @@ -38,6 +39,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
private labelColorStyles = {};
private itemStyles = new Map<string, CssClassMap>();
private clickListener?: (ev: Event) => void;
private inheritedAttributes: Attributes = {};

@Element() el!: HTMLIonItemElement;

Expand Down Expand Up @@ -226,6 +228,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac

componentDidLoad() {
raf(() => {
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
this.setMultipleInputs();
this.focusable = this.isFocusable();
});
Expand Down Expand Up @@ -359,15 +362,18 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
target,
routerAnimation,
routerDirection,
inheritedAttributes,
} = this;
const childStyles = {} as any;
const mode = getIonMode(this);
const clickable = this.isClickable();
const canActivate = this.canActivate();
const TagType = clickable ? (href === undefined ? 'button' : 'a') : ('div' as any);
const { ['aria-label']: ariaLabel } = inheritedAttributes;

const attrs =
TagType === 'button'
? { type: this.type }
? { type: this.type, ['aria-label']: ariaLabel }
sean-perkins marked this conversation as resolved.
Show resolved Hide resolved
: {
download,
href,
Expand All @@ -390,6 +396,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
const ariaDisabled = disabled || childStyles['item-interactive-disabled'] ? 'true' : null;
const fillValue = fill || 'none';
const inList = hostContext('ion-list', this.el);

return (
<Host
aria-disabled={ariaDisabled}
Expand Down
9 changes: 7 additions & 2 deletions core/src/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,14 @@ const ariaAttributes = [
* Returns an array of aria attributes that should be copied from
* the shadow host element to a target within the light DOM.
* @param el The element that the attributes should be copied from.
* @param ignoreList The list of aria-attributes to ignore reflecting and removing from the host. Use this in instances where we manually specify aria attributes on the `<Host>` element.
*/
export const inheritAriaAttributes = (el: HTMLElement) => {
return inheritAttributes(el, ariaAttributes);
export const inheritAriaAttributes = (el: HTMLElement, ignoreList?: string[]) => {
sean-perkins marked this conversation as resolved.
Show resolved Hide resolved
let attributesToInherit = ariaAttributes;
if (ignoreList && ignoreList.length > 0) {
attributesToInherit = attributesToInherit.filter((attr) => !ignoreList.includes(attr));
}
return inheritAttributes(el, attributesToInherit);
};

export const addEventListener = (el: any, eventName: string, callback: any, opts?: any) => {
Expand Down