mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-10 00:27:41 +08:00
chore(): resolve merge conflicts
This commit is contained in:
4
core/package-lock.json
generated
4
core/package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "5.6.3",
|
||||
"version": "5.6.5",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/core",
|
||||
"version": "5.6.3",
|
||||
"version": "5.6.5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^2.4.0",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "5.6.3",
|
||||
"version": "5.6.5",
|
||||
"description": "Base components for Ionic",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
|
||||
@ -418,7 +418,7 @@ Developers can also use this component directly in their template:
|
||||
header="Albums"
|
||||
css-class="my-custom-class"
|
||||
:buttons="buttons"
|
||||
@onDidDismiss="setOpen(false)"
|
||||
@didDismiss="setOpen(false)"
|
||||
>
|
||||
</ion-action-sheet>
|
||||
</template>
|
||||
|
||||
@ -76,7 +76,7 @@ Developers can also use this component directly in their template:
|
||||
header="Albums"
|
||||
css-class="my-custom-class"
|
||||
:buttons="buttons"
|
||||
@onDidDismiss="setOpen(false)"
|
||||
@didDismiss="setOpen(false)"
|
||||
>
|
||||
</ion-action-sheet>
|
||||
</template>
|
||||
|
||||
@ -1664,7 +1664,7 @@ Developers can also use this component directly in their template:
|
||||
message="This is an alert message."
|
||||
css-class="my-custom-class"
|
||||
:buttons="buttons"
|
||||
@onDidDismiss="setOpen(false)"
|
||||
@didDismiss="setOpen(false)"
|
||||
>
|
||||
</ion-alert>
|
||||
</template>
|
||||
|
||||
@ -315,7 +315,7 @@ Developers can also use this component directly in their template:
|
||||
message="This is an alert message."
|
||||
css-class="my-custom-class"
|
||||
:buttons="buttons"
|
||||
@onDidDismiss="setOpen(false)"
|
||||
@didDismiss="setOpen(false)"
|
||||
>
|
||||
</ion-alert>
|
||||
</template>
|
||||
|
||||
@ -26,6 +26,7 @@ export class Content implements ComponentInterface {
|
||||
private cTop = -1;
|
||||
private cBottom = -1;
|
||||
private scrollEl!: HTMLElement;
|
||||
private isMainContent = true;
|
||||
|
||||
// Detail is used in a hot loop in the scroll event, by allocating it here
|
||||
// V8 will be able to inline any read/write to it since it's a monomorphic class.
|
||||
@ -104,6 +105,10 @@ export class Content implements ComponentInterface {
|
||||
*/
|
||||
@Event() ionScrollEnd!: EventEmitter<ScrollBaseDetail>;
|
||||
|
||||
connectedCallback() {
|
||||
this.isMainContent = this.el.closest('ion-menu, ion-popover, ion-modal') === null;
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.onScrollEnd();
|
||||
}
|
||||
@ -303,10 +308,11 @@ export class Content implements ComponentInterface {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { scrollX, scrollY } = this;
|
||||
const { isMainContent, scrollX, scrollY } = this;
|
||||
const mode = getIonMode(this);
|
||||
const forceOverscroll = this.shouldForceOverscroll();
|
||||
const transitionShadow = mode === 'ios';
|
||||
const TagType = isMainContent ? 'main' : 'div' as any;
|
||||
|
||||
this.resize();
|
||||
|
||||
@ -323,19 +329,19 @@ export class Content implements ComponentInterface {
|
||||
}}
|
||||
>
|
||||
<div id="background-content" part="background"></div>
|
||||
<main
|
||||
<TagType
|
||||
class={{
|
||||
'inner-scroll': true,
|
||||
'scroll-x': scrollX,
|
||||
'scroll-y': scrollY,
|
||||
'overscroll': (scrollX || scrollY) && forceOverscroll
|
||||
}}
|
||||
ref={el => this.scrollEl = el!}
|
||||
onScroll={(this.scrollEvents) ? ev => this.onScroll(ev) : undefined}
|
||||
ref={(el: HTMLElement) => this.scrollEl = el!}
|
||||
onScroll={(this.scrollEvents) ? (ev: UIEvent) => this.onScroll(ev) : undefined}
|
||||
part="scroll"
|
||||
>
|
||||
<slot></slot>
|
||||
</main>
|
||||
</TagType>
|
||||
|
||||
{transitionShadow ? (
|
||||
<div class="transition-effect">
|
||||
|
||||
@ -641,7 +641,7 @@ export class Datetime implements ComponentInterface {
|
||||
aria-disabled={disabled ? 'true' : null}
|
||||
aria-expanded={`${isExpanded}`}
|
||||
aria-haspopup="true"
|
||||
aria-labelledby={labelId}
|
||||
aria-labelledby={label ? labelId : null}
|
||||
class={{
|
||||
[mode]: true,
|
||||
'datetime-disabled': disabled,
|
||||
|
||||
30
core/src/components/datetime/test/a11y/datetime.spec.ts
Normal file
30
core/src/components/datetime/test/a11y/datetime.spec.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
import { Datetime } from '../../datetime';
|
||||
import { Item } from '../../../item/item';
|
||||
import { Label } from '../../../label/label';
|
||||
|
||||
describe('Datetime a11y', () => {
|
||||
it('does not set a default aria-labelledby when there is not a neighboring ion-label', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Datetime, Item, Label],
|
||||
html: `<ion-datetime></ion-datetime>`
|
||||
})
|
||||
|
||||
const ariaLabelledBy = page.root.getAttribute('aria-labelledby');
|
||||
expect(ariaLabelledBy).toBe(null);
|
||||
});
|
||||
|
||||
it('set a default aria-labelledby when a neighboring ion-label exists', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Datetime, Item, Label],
|
||||
html: `<ion-item>
|
||||
<ion-label>A11y Test</ion-label>
|
||||
<ion-datetime></ion-datetime>
|
||||
</ion-item>`
|
||||
})
|
||||
|
||||
const label = page.body.querySelector('ion-label');
|
||||
const ariaLabelledBy = page.body.querySelector('ion-datetime').getAttribute('aria-labelledby');
|
||||
expect(ariaLabelledBy).toBe(label.id);
|
||||
});
|
||||
});
|
||||
@ -12,6 +12,6 @@
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script></head>
|
||||
|
||||
<body>
|
||||
<ion-datetime id="basic" display-format="MMMM" value="2012-12-15T13:47:20.789"></ion-datetime>
|
||||
<ion-datetime id="basic" display-format="MMMM" value="2012-12-15T13:47:20.789" aria-label="datetime picker"></ion-datetime>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -234,7 +234,7 @@ export class Input implements ComponentInterface {
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
this.inheritedAttributes = inheritAttributes(this.el, ['tabindex', 'title']);
|
||||
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label', 'tabindex', 'title']);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
@ -408,7 +408,7 @@ export class Input implements ComponentInterface {
|
||||
<input
|
||||
class="native-input"
|
||||
ref={input => this.nativeInput = input}
|
||||
aria-labelledby={labelId}
|
||||
aria-labelledby={label ? labelId : null}
|
||||
disabled={this.disabled}
|
||||
accept={this.accept}
|
||||
autoCapitalize={this.autocapitalize}
|
||||
|
||||
30
core/src/components/input/test/a11y/input.spec.ts
Normal file
30
core/src/components/input/test/a11y/input.spec.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
import { Input } from '../../input';
|
||||
import { Item } from '../../../item/item';
|
||||
import { Label } from '../../../label/label';
|
||||
|
||||
describe('Input a11y', () => {
|
||||
it('does not set a default aria-labelledby when there is not a neighboring ion-label', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Input, Item, Label],
|
||||
html: `<ion-input></ion-input>`
|
||||
})
|
||||
|
||||
const ariaLabelledBy = page.body.querySelector('ion-input > input').getAttribute('aria-labelledby');
|
||||
expect(ariaLabelledBy).toBe(null);
|
||||
});
|
||||
|
||||
it('set a default aria-labelledby when a neighboring ion-label exists', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Input, Item, Label],
|
||||
html: `<ion-item>
|
||||
<ion-label>A11y Test</ion-label>
|
||||
<ion-input></ion-input>
|
||||
</ion-item>`
|
||||
})
|
||||
|
||||
const label = page.body.querySelector('ion-label');
|
||||
const ariaLabelledBy = page.body.querySelector('ion-input > input').getAttribute('aria-labelledby');
|
||||
expect(ariaLabelledBy).toBe(label.id);
|
||||
});
|
||||
});
|
||||
@ -46,6 +46,10 @@
|
||||
<ion-input id="input3" value="inputs"></ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-input id="input4" placeholder="No Label" aria-label="input4"></ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
Number Test
|
||||
@ -59,6 +63,10 @@
|
||||
Default Test
|
||||
<span id="defaultInputResult"></span>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
No Label Test
|
||||
<span id="noLabelInputResult"></span>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
</ion-list>
|
||||
@ -74,6 +82,9 @@
|
||||
var defaultInput = checkInput('input3');
|
||||
updateResult(defaultInput, 'defaultInputResult');
|
||||
|
||||
var noLabelInput = checkInput('input4');
|
||||
updateResult(noLabelInput, 'noLabelInputResult');
|
||||
|
||||
// Update results of input
|
||||
function updateResult(result, resultId) {
|
||||
var resultEl = document.getElementById(resultId);
|
||||
@ -122,6 +133,14 @@
|
||||
readonly: undefined,
|
||||
disabled: undefined
|
||||
});
|
||||
} else if (id === 'input4') {
|
||||
return testAttributes(el, inputEl, {
|
||||
id: 'input4',
|
||||
type: undefined,
|
||||
readonly: undefined,
|
||||
disabled: undefined,
|
||||
'aria-label': 'input4'
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -312,7 +312,7 @@ Developers can also use this component directly in their template:
|
||||
cssClass="my-custom-class"
|
||||
message="Please wait..."
|
||||
:duration="timeout"
|
||||
@onDidDismiss="setOpen(false)"
|
||||
@didDismiss="setOpen(false)"
|
||||
>
|
||||
</ion-loading>
|
||||
</template>
|
||||
|
||||
@ -63,7 +63,7 @@ Developers can also use this component directly in their template:
|
||||
cssClass="my-custom-class"
|
||||
message="Please wait..."
|
||||
:duration="timeout"
|
||||
@onDidDismiss="setOpen(false)"
|
||||
@didDismiss="setOpen(false)"
|
||||
>
|
||||
</ion-loading>
|
||||
</template>
|
||||
|
||||
@ -717,7 +717,7 @@ Developers can also use this component directly in their template:
|
||||
<ion-modal
|
||||
:is-open="isOpenRef"
|
||||
css-class="my-custom-class"
|
||||
@onDidDismiss="setOpen(false)"
|
||||
@didDismiss="setOpen(false)"
|
||||
>
|
||||
<Modal :data="data"></Modal>
|
||||
</ion-modal>
|
||||
|
||||
@ -69,7 +69,7 @@ Developers can also use this component directly in their template:
|
||||
<ion-modal
|
||||
:is-open="isOpenRef"
|
||||
css-class="my-custom-class"
|
||||
@onDidDismiss="setOpen(false)"
|
||||
@didDismiss="setOpen(false)"
|
||||
>
|
||||
<Modal :data="data"></Modal>
|
||||
</ion-modal>
|
||||
|
||||
@ -330,7 +330,7 @@ Developers can also use this component directly in their template:
|
||||
css-class="my-custom-class"
|
||||
:event="event"
|
||||
:translucent="true"
|
||||
@onDidDismiss="setOpen(false)"
|
||||
@didDismiss="setOpen(false)"
|
||||
>
|
||||
<Popover></Popover>
|
||||
</ion-popover>
|
||||
|
||||
@ -60,7 +60,7 @@ Developers can also use this component directly in their template:
|
||||
css-class="my-custom-class"
|
||||
:event="event"
|
||||
:translucent="true"
|
||||
@onDidDismiss="setOpen(false)"
|
||||
@didDismiss="setOpen(false)"
|
||||
>
|
||||
<Popover></Popover>
|
||||
</ion-popover>
|
||||
|
||||
@ -141,10 +141,11 @@ export class RadioGroup implements ComponentInterface {
|
||||
}
|
||||
|
||||
// Update the radio group value when a user presses the
|
||||
// space bar on top of a selected radio (only applies
|
||||
// to radios in a select popover)
|
||||
// space bar on top of a selected radio
|
||||
if (['Space'].includes(ev.code)) {
|
||||
this.value = current.value;
|
||||
this.value = (this.allowEmptySelection && this.value !== undefined)
|
||||
? undefined
|
||||
: current.value;
|
||||
|
||||
// Prevent browsers from jumping
|
||||
// to the bottom of the screen
|
||||
|
||||
88
core/src/components/radio-group/test/radio-group.e2e.ts
Normal file
88
core/src/components/radio-group/test/radio-group.e2e.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
/**
|
||||
* @param page the E2E page that contains the radio button
|
||||
* @param radioButtonId the id of the radio button to focus
|
||||
* @returns the checked property of the focused radio button
|
||||
*/
|
||||
const selectRadio = async (page, radioButtonId: string, selectionMethod: 'keyboard' | 'mouse'): Promise<boolean> => {
|
||||
const selector = `ion-radio#${radioButtonId}`;
|
||||
if (selectionMethod === 'keyboard') {
|
||||
await page.focus(selector);
|
||||
await page.keyboard.press('Space');
|
||||
} else if (selectionMethod === 'mouse') {
|
||||
await page.click(selector);
|
||||
}
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
const radioGroup = await page.find(`ion-radio#${radioButtonId} >>> input`);
|
||||
const checked = await radioGroup.getProperty('checked');
|
||||
return checked;
|
||||
}
|
||||
|
||||
describe('radio-group', () => {
|
||||
it('Spacebar should not deselect without allowEmptySelection', async () => {
|
||||
const page = await newE2EPage();
|
||||
await page.setContent(`
|
||||
<ion-radio-group value="one" allow-empty-selection="false">
|
||||
<ion-item>
|
||||
<ion-label>One</ion-label>
|
||||
<ion-radio id="one" value="one"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
`);
|
||||
|
||||
const checked = await selectRadio(page, 'one', 'keyboard');
|
||||
|
||||
expect(checked).toBe(true);
|
||||
});
|
||||
|
||||
it('Spacebar should deselect with allowEmptySelection', async () => {
|
||||
const page = await newE2EPage();
|
||||
await page.setContent(`
|
||||
<ion-radio-group value="one" allow-empty-selection="true">
|
||||
<ion-item>
|
||||
<ion-label>One</ion-label>
|
||||
<ion-radio id="one" value="one"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
`);
|
||||
|
||||
const checked = await selectRadio(page, 'one', 'keyboard');
|
||||
|
||||
expect(checked).toBe(false);
|
||||
});
|
||||
|
||||
it('Click should not deselect without allowEmptySelection', async () => {
|
||||
const page = await newE2EPage();
|
||||
await page.setContent(`
|
||||
<ion-radio-group value="one" allow-empty-selection="false">
|
||||
<ion-item>
|
||||
<ion-label>One</ion-label>
|
||||
<ion-radio id="one" value="one"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
`);
|
||||
|
||||
const checked = await selectRadio(page, 'one', 'mouse');
|
||||
|
||||
expect(checked).toBe(true);
|
||||
});
|
||||
|
||||
it('Click should deselect with allowEmptySelection', async () => {
|
||||
const page = await newE2EPage();
|
||||
await page.setContent(`
|
||||
<ion-radio-group value="one" allow-empty-selection="true">
|
||||
<ion-item>
|
||||
<ion-label>One</ion-label>
|
||||
<ion-radio id="one" value="one"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
`);
|
||||
|
||||
const checked = await selectRadio(page, 'one', 'mouse');
|
||||
|
||||
expect(checked).toBe(false);
|
||||
});
|
||||
});
|
||||
@ -166,6 +166,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
:host(:focus) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
// Segment Button: Hover
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
@ -86,13 +86,28 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
|
||||
}
|
||||
}
|
||||
|
||||
private get tabIndex() {
|
||||
if (this.disabled) { return -1; }
|
||||
|
||||
const hasTabIndex = this.el.hasAttribute('tabindex');
|
||||
|
||||
if (hasTabIndex) {
|
||||
return this.el.getAttribute('tabindex');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { checked, type, disabled, hasIcon, hasLabel, layout, segmentEl } = this;
|
||||
const { checked, type, disabled, hasIcon, hasLabel, layout, segmentEl, tabIndex } = this;
|
||||
const mode = getIonMode(this);
|
||||
const hasSegmentColor = () => segmentEl !== null && segmentEl.color !== undefined;
|
||||
return (
|
||||
<Host
|
||||
role="tab"
|
||||
aria-selected={checked ? 'true' : 'false'}
|
||||
aria-disabled={disabled ? 'true' : null}
|
||||
tabIndex={tabIndex}
|
||||
class={{
|
||||
[mode]: true,
|
||||
'in-toolbar': hostContext('ion-toolbar', this.el),
|
||||
@ -113,7 +128,7 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
|
||||
>
|
||||
<button
|
||||
type={type}
|
||||
aria-pressed={checked ? 'true' : 'false'}
|
||||
tabIndex={-1}
|
||||
class="button-native"
|
||||
part="native"
|
||||
disabled={disabled}
|
||||
|
||||
@ -425,6 +425,7 @@ export class Segment implements ComponentInterface {
|
||||
const mode = getIonMode(this);
|
||||
return (
|
||||
<Host
|
||||
role="tablist"
|
||||
onClick={this.onClick}
|
||||
class={createColorClasses(this.color, {
|
||||
[mode]: true,
|
||||
|
||||
11
core/src/components/segment/test/a11y/e2e.ts
Normal file
11
core/src/components/segment/test/a11y/e2e.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
import { AxePuppeteer } from '@axe-core/puppeteer';
|
||||
|
||||
test('segment: axe', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/segment/test/a11y?ionic:_testing=true'
|
||||
});
|
||||
|
||||
const results = await new AxePuppeteer(page).analyze();
|
||||
expect(results.violations.length).toEqual(0);
|
||||
});
|
||||
31
core/src/components/segment/test/a11y/index.html
Normal file
31
core/src/components/segment/test/a11y/index.html
Normal file
@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Segment - a11y</title>
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
||||
<link href="../../../../../css/core.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>
|
||||
<main>
|
||||
<h1>Segment</h1>
|
||||
<ion-segment aria-label="Tab Options" color="dark" value="reading-list">
|
||||
<ion-segment-button value="bookmarks">
|
||||
<ion-label>Bookmarks</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="reading-list">
|
||||
<ion-label>Reading List</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="shared-links">
|
||||
<ion-label>Shared Links</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
24
core/src/components/segment/test/a11y/screen-readers.md
Normal file
24
core/src/components/segment/test/a11y/screen-readers.md
Normal file
@ -0,0 +1,24 @@
|
||||
"native" refers to this sample: https://w3c.github.io/aria-practices/examples/tabs/tabs-2/tabs.html
|
||||
|
||||
### Tabbing to Segment Button
|
||||
|
||||
| | native | Ionic |
|
||||
| ------------------------ | ------------------------------------------------ | ------------------------------------------------ |
|
||||
| VoiceOver macOS - Chrome | BOOKMARKS, tab, 1 of 3, Tab Options, tab group | BOOKMARKS, tab, 1 of 3, Tab Options, tab group |
|
||||
| VoiceOver macOS - Safari | BOOKMARKS, tab, 1 of 3, Tab Options, tab group | BOOKMARKS, tab, 1 of 3, Tab Options, tab group |
|
||||
| VoiceOver iOS | Bookmarks, tab | Bookmarks, tab |
|
||||
| Android TalkBack | Bookmarks, tab | Bookmarks, tab |
|
||||
| Windows NVDA | Tab Options, tab control, BOOKMARKS, tab, 1 of 3 | Tab Options, tab control, BOOKMARKS, tab, 1 of 3 |
|
||||
|
||||
### Selecting Segment Button
|
||||
|
||||
| | native | Ionic |
|
||||
| ------------------------ | -------------------------------------------------------- | ------------------------ |
|
||||
| VoiceOver macOS - Chrome | BOOKMARKS, selected, tab, 1 of 3, Tab Options, tab group | BOOKMARKS, selected, tab, 1 of 3, Tab Options, tab group |
|
||||
| VoiceOver macOS - Safari | BOOKMARKS, selected, tab, 1 of 3, Tab Options, tab group | BOOKMARKS, selected, tab, 1 of 3, Tab Options, tab group |
|
||||
| VoiceOver iOS | selected, Bookmarks, tab | selected, Bookmarks, tab |
|
||||
| Android TalkBack | selected | selected |
|
||||
| Windows NVDA | BOOKMARKS, tab, 1 of 3, selected | BOOKMARKS, tab, 1 of 3, selected |
|
||||
|
||||
Note: The `aria-label` for tablist is typically only read on the first interaction.
|
||||
|
||||
@ -148,7 +148,7 @@
|
||||
console.log('slide transition start', e)
|
||||
});
|
||||
slides.addEventListener('ionSlideTransitionEnd', function (e) {
|
||||
console.log('slide transistion end', e)
|
||||
console.log('slide transition end', e)
|
||||
});
|
||||
slides.addEventListener('ionSlideDrag', function (e) {
|
||||
console.log('slide drag', e)
|
||||
|
||||
30
core/src/components/textarea/test/a11y/textarea.spec.ts
Normal file
30
core/src/components/textarea/test/a11y/textarea.spec.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
import { Textarea } from '../../textarea';
|
||||
import { Item } from '../../../item/item';
|
||||
import { Label } from '../../../label/label';
|
||||
|
||||
describe('Textarea a11y', () => {
|
||||
it('does not set a default aria-labelledby when there is not a neighboring ion-label', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Textarea, Item, Label],
|
||||
html: `<ion-textarea></ion-textarea>`
|
||||
})
|
||||
|
||||
const ariaLabelledBy = page.body.querySelector('ion-textarea textarea').getAttribute('aria-labelledby');
|
||||
expect(ariaLabelledBy).toBe(null);
|
||||
});
|
||||
|
||||
it('set a default aria-labelledby when a neighboring ion-label exists', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Textarea, Item, Label],
|
||||
html: `<ion-item>
|
||||
<ion-label>A11y Test</ion-label>
|
||||
<ion-textarea></ion-textarea>
|
||||
</ion-item>`
|
||||
})
|
||||
|
||||
const label = page.body.querySelector('ion-label');
|
||||
const ariaLabelledBy = page.body.querySelector('ion-textarea textarea').getAttribute('aria-labelledby');
|
||||
expect(ariaLabelledBy).toBe(label.id);
|
||||
});
|
||||
});
|
||||
@ -363,7 +363,7 @@ export class Textarea implements ComponentInterface {
|
||||
>
|
||||
<textarea
|
||||
class="native-textarea"
|
||||
aria-labelledby={labelId}
|
||||
aria-labelledby={label ? labelId : null}
|
||||
ref={el => this.nativeInput = el}
|
||||
autoCapitalize={this.autocapitalize}
|
||||
autoFocus={this.autofocus}
|
||||
|
||||
@ -334,7 +334,7 @@ Developers can also use this component directly in their template:
|
||||
:is-open="isOpenRef"
|
||||
message="Your settings have been saved."
|
||||
:duration="2000"
|
||||
@onDidDismiss="setOpen(false)"
|
||||
@didDismiss="setOpen(false)"
|
||||
>
|
||||
</ion-toast>
|
||||
</template>
|
||||
|
||||
@ -64,7 +64,7 @@ Developers can also use this component directly in their template:
|
||||
:is-open="isOpenRef"
|
||||
message="Your settings have been saved."
|
||||
:duration="2000"
|
||||
@onDidDismiss="setOpen(false)"
|
||||
@didDismiss="setOpen(false)"
|
||||
>
|
||||
</ion-toast>
|
||||
</template>
|
||||
|
||||
@ -76,6 +76,11 @@
|
||||
<ion-toggle slot="start" style="--border-radius: 0px;--handle-border-radius: 0px;" checked></ion-toggle>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Stop Immediate Event Propagation</ion-label>
|
||||
<ion-toggle slot="start" checked id="eventPropagation"></ion-toggle>
|
||||
</ion-item>
|
||||
|
||||
</ion-list>
|
||||
|
||||
|
||||
@ -121,6 +126,11 @@
|
||||
var isTrue = el[prop] ? false : true;
|
||||
el[prop] = isTrue;
|
||||
}
|
||||
|
||||
document.getElementById('eventPropagation').addEventListener('click', (evt) => {
|
||||
evt.stopImmediatePropagation();
|
||||
console.log('clicked');
|
||||
});
|
||||
</script>
|
||||
|
||||
</ion-app>
|
||||
|
||||
@ -53,6 +53,8 @@ label {
|
||||
align-items: center;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
input {
|
||||
|
||||
Reference in New Issue
Block a user