chore(): resolve merge conflicts

This commit is contained in:
Liam DeBeasi
2021-04-23 11:41:46 -04:00
120 changed files with 21337 additions and 2677 deletions

View File

@ -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",

View File

@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "5.6.3",
"version": "5.6.5",
"description": "Base components for Ionic",
"keywords": [
"ionic",

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -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,

View 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);
});
});

View File

@ -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>

View File

@ -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}

View 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);
});
});

View File

@ -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&nbsp;
@ -59,6 +63,10 @@
Default Test&nbsp;
<span id="defaultInputResult"></span>
</ion-item>
<ion-item>
No Label Test&nbsp;
<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;
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View 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);
});
});

View File

@ -166,6 +166,10 @@
}
}
:host(:focus) {
outline: none;
}
// Segment Button: Hover
// --------------------------------------------------

View File

@ -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}

View File

@ -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,

View 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);
});

View 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>

View 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.

View File

@ -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)

View 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);
});
});

View File

@ -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}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -53,6 +53,8 @@ label {
align-items: center;
opacity: 0;
pointer-events: none;
}
input {