mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 10:01:59 +08:00
fix(item): inherit aria attributes before render (#26546)
Resolves #26538
This commit is contained in:
19
core/package-lock.json
generated
19
core/package-lock.json
generated
@ -24,7 +24,7 @@
|
||||
"@stencil/angular-output-target": "^0.4.0",
|
||||
"@stencil/react-output-target": "^0.2.1",
|
||||
"@stencil/sass": "^2.0.0",
|
||||
"@stencil/vue-output-target": "^0.6.2",
|
||||
"@stencil/vue-output-target": "^0.7.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^14.6.0",
|
||||
"@types/swiper": "5.4.0",
|
||||
@ -1632,12 +1632,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@stencil/vue-output-target": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.6.2.tgz",
|
||||
"integrity": "sha512-Oh7SLFbOUchCSCbGe/Dqal2xSYPKCFQiVKnvzvS0dsHP/XS7rfHqp3qptW6JCp9lBoo3wmmBurHfldqxhLlnag==",
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.7.0.tgz",
|
||||
"integrity": "sha512-iPEgnT2z6HsfWVRWVZk5C1AaMZnbJjB+c/hvtWoO7B3aErTJB8Up6oFk/t3IRsr12aNuZ4fUra0FEDx9WweH0Q==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"@stencil/core": "^2.9.0"
|
||||
"@stencil/core": "^2.9.0 || ^3.0.0-beta.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@stylelint/postcss-css-in-js": {
|
||||
@ -11638,11 +11638,10 @@
|
||||
"requires": {}
|
||||
},
|
||||
"@stencil/vue-output-target": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.6.2.tgz",
|
||||
"integrity": "sha512-Oh7SLFbOUchCSCbGe/Dqal2xSYPKCFQiVKnvzvS0dsHP/XS7rfHqp3qptW6JCp9lBoo3wmmBurHfldqxhLlnag==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.7.0.tgz",
|
||||
"integrity": "sha512-iPEgnT2z6HsfWVRWVZk5C1AaMZnbJjB+c/hvtWoO7B3aErTJB8Up6oFk/t3IRsr12aNuZ4fUra0FEDx9WweH0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"@stylelint/postcss-css-in-js": {
|
||||
"version": "0.37.2",
|
||||
|
@ -46,7 +46,7 @@
|
||||
"@stencil/angular-output-target": "^0.4.0",
|
||||
"@stencil/react-output-target": "^0.2.1",
|
||||
"@stencil/sass": "^2.0.0",
|
||||
"@stencil/vue-output-target": "^0.6.2",
|
||||
"@stencil/vue-output-target": "^0.7.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^14.6.0",
|
||||
"@types/swiper": "5.4.0",
|
||||
|
@ -226,9 +226,12 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
|
||||
}
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
this.inheritedAriaAttributes = inheritAttributes(this.el, ['aria-label']);
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
raf(() => {
|
||||
this.inheritedAriaAttributes = inheritAttributes(this.el, ['aria-label']);
|
||||
this.setMultipleInputs();
|
||||
this.focusable = this.isFocusable();
|
||||
});
|
||||
|
@ -12,4 +12,17 @@ test.describe('item: axe', () => {
|
||||
.analyze();
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test('should reflect aria-label', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<ion-item id="item-1" aria-label="test"></ion-item>
|
||||
<ion-item id="item-2" aria-label="test" button="true"></ion-item>
|
||||
`);
|
||||
|
||||
const item1 = await page.locator('#item-1 .item-native');
|
||||
const item2 = await page.locator('#item-2 .item-native');
|
||||
|
||||
expect(await item1.getAttribute('aria-label')).toEqual('test');
|
||||
expect(await item2.getAttribute('aria-label')).toEqual('test');
|
||||
});
|
||||
});
|
||||
|
@ -9,7 +9,7 @@ const MODEL_VALUE = 'modelValue';
|
||||
const ROUTER_LINK_VALUE = 'routerLink';
|
||||
const NAV_MANAGER = 'navManager';
|
||||
const ROUTER_PROP_PREFIX = 'router';
|
||||
|
||||
const ARIA_PROP_PREFIX = 'aria';
|
||||
/**
|
||||
* Starting in Vue 3.1.0, all properties are
|
||||
* added as keys to the props object, even if
|
||||
@ -30,26 +30,31 @@ const getComponentClasses = (classes: unknown) => {
|
||||
return (classes as string)?.split(' ') || [];
|
||||
};
|
||||
|
||||
const getElementClasses = (ref: Ref<HTMLElement | undefined>, componentClasses: Set<string>, defaultClasses: string[] = []) => {
|
||||
return [ ...Array.from(ref.value?.classList || []), ...defaultClasses ]
|
||||
.filter((c: string, i, self) => !componentClasses.has(c) && self.indexOf(c) === i);
|
||||
const getElementClasses = (
|
||||
ref: Ref<HTMLElement | undefined>,
|
||||
componentClasses: Set<string>,
|
||||
defaultClasses: string[] = []
|
||||
) => {
|
||||
return [...Array.from(ref.value?.classList || []), ...defaultClasses].filter(
|
||||
(c: string, i, self) => !componentClasses.has(c) && self.indexOf(c) === i
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a callback to define a Vue component wrapper around a Web Component.
|
||||
*
|
||||
* @prop name - The component tag name (i.e. `ion-button`)
|
||||
* @prop componentProps - An array of properties on the
|
||||
* component. These usually match up with the @Prop definitions
|
||||
* in each component's TSX file.
|
||||
* @prop customElement - An option custom element instance to pass
|
||||
* to customElements.define. Only set if `includeImportCustomElements: true` in your config.
|
||||
* @prop modelProp - The prop that v-model binds to (i.e. value)
|
||||
* @prop modelUpdateEvent - The event that is fired from your Web Component when the value changes (i.e. ionChange)
|
||||
* @prop externalModelUpdateEvent - The external event to fire from your Vue component when modelUpdateEvent fires. This is used for ensuring that v-model references have been
|
||||
* correctly updated when a user's event callback fires.
|
||||
*/
|
||||
export const defineContainer = <Props, VModelType=string|number|boolean>(
|
||||
* Create a callback to define a Vue component wrapper around a Web Component.
|
||||
*
|
||||
* @prop name - The component tag name (i.e. `ion-button`)
|
||||
* @prop componentProps - An array of properties on the
|
||||
* component. These usually match up with the @Prop definitions
|
||||
* in each component's TSX file.
|
||||
* @prop customElement - An option custom element instance to pass
|
||||
* to customElements.define. Only set if `includeImportCustomElements: true` in your config.
|
||||
* @prop modelProp - The prop that v-model binds to (i.e. value)
|
||||
* @prop modelUpdateEvent - The event that is fired from your Web Component when the value changes (i.e. ionChange)
|
||||
* @prop externalModelUpdateEvent - The external event to fire from your Vue component when modelUpdateEvent fires. This is used for ensuring that v-model references have been
|
||||
* correctly updated when a user's event callback fires.
|
||||
*/
|
||||
export const defineContainer = <Props, VModelType = string | number | boolean>(
|
||||
name: string,
|
||||
defineCustomElement: any,
|
||||
componentProps: string[] = [],
|
||||
@ -58,10 +63,10 @@ export const defineContainer = <Props, VModelType=string|number|boolean>(
|
||||
externalModelUpdateEvent?: string
|
||||
) => {
|
||||
/**
|
||||
* Create a Vue component wrapper around a Web Component.
|
||||
* Note: The `props` here are not all properties on a component.
|
||||
* They refer to whatever properties are set on an instance of a component.
|
||||
*/
|
||||
* Create a Vue component wrapper around a Web Component.
|
||||
* Note: The `props` here are not all properties on a component.
|
||||
* They refer to whatever properties are set on an instance of a component.
|
||||
*/
|
||||
|
||||
if (defineCustomElement !== undefined) {
|
||||
defineCustomElement();
|
||||
@ -116,12 +121,12 @@ export const defineContainer = <Props, VModelType=string|number|boolean>(
|
||||
} else {
|
||||
console.warn('Tried to navigate, but no router was found. Make sure you have mounted Vue Router.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return () => {
|
||||
modelPropValue = props[modelProp];
|
||||
|
||||
getComponentClasses(attrs.class).forEach(value => {
|
||||
getComponentClasses(attrs.class).forEach((value) => {
|
||||
classes.add(value);
|
||||
});
|
||||
|
||||
@ -133,13 +138,13 @@ export const defineContainer = <Props, VModelType=string|number|boolean>(
|
||||
if (!ev.defaultPrevented) {
|
||||
handleRouterLink(ev);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let propsToAdd: any = {
|
||||
ref: containerRef,
|
||||
class: getElementClasses(containerRef, classes),
|
||||
onClick: handleClick,
|
||||
onVnodeBeforeMount: (modelUpdateEvent) ? onVnodeBeforeMount : undefined
|
||||
onVnodeBeforeMount: modelUpdateEvent ? onVnodeBeforeMount : undefined,
|
||||
};
|
||||
|
||||
/**
|
||||
@ -150,7 +155,7 @@ export const defineContainer = <Props, VModelType=string|number|boolean>(
|
||||
*/
|
||||
for (const key in props) {
|
||||
const value = props[key];
|
||||
if (props.hasOwnProperty(key) && value !== EMPTY_PROP) {
|
||||
if ((props.hasOwnProperty(key) && value !== EMPTY_PROP) || key.startsWith(ARIA_PROP_PREFIX)) {
|
||||
propsToAdd[key] = value;
|
||||
}
|
||||
}
|
||||
@ -165,27 +170,27 @@ export const defineContainer = <Props, VModelType=string|number|boolean>(
|
||||
if (props[MODEL_VALUE] !== EMPTY_PROP) {
|
||||
propsToAdd = {
|
||||
...propsToAdd,
|
||||
[modelProp]: props[MODEL_VALUE]
|
||||
}
|
||||
[modelProp]: props[MODEL_VALUE],
|
||||
};
|
||||
} else if (modelPropValue !== EMPTY_PROP) {
|
||||
propsToAdd = {
|
||||
...propsToAdd,
|
||||
[modelProp]: modelPropValue
|
||||
}
|
||||
[modelProp]: modelPropValue,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return h(name, propsToAdd, slots.default && slots.default());
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Container.displayName = name;
|
||||
|
||||
Container.props = {
|
||||
[ROUTER_LINK_VALUE]: DEFAULT_EMPTY_PROP
|
||||
[ROUTER_LINK_VALUE]: DEFAULT_EMPTY_PROP,
|
||||
};
|
||||
|
||||
componentProps.forEach(componentProp => {
|
||||
componentProps.forEach((componentProp) => {
|
||||
Container.props[componentProp] = DEFAULT_EMPTY_PROP;
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user