chore(): sync with main

This commit is contained in:
Liam DeBeasi
2021-07-20 12:50:52 -04:00
48 changed files with 868 additions and 240 deletions

View File

@ -1,60 +0,0 @@
---
name: Bug Report
about: Create a report to help us improve
title: 'bug: '
labels: ''
assignees: ''
---
<!-- Before submitting an issue, please consult our docs (https://ionicframework.com/docs/). -->
<!-- Please make sure you are posting an issue pertaining to the Ionic Framework. If you are having an issue with the Ionic Appflow services (Ionic View, Ionic Deploy, etc.) please consult the Ionic Appflow support portal (https://ionic.zendesk.com/hc/en-us) -->
<!-- Please do not submit support requests or "How to" questions here. Instead, please use the Ionic Forum: https://forum.ionicframework.com/ -->
<!-- ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION. -->
# Bug Report
**Ionic version:**
<!-- (For Ionic 1.x issues, please use https://github.com/ionic-team/ionic-v1) -->
<!-- (For Ionic 2.x & 3.x issues, please use https://github.com/ionic-team/ionic-v3) -->
[ ] **4.x**
[ ] **5.x**
[ ] **6.x**
**Current behavior:**
<!-- Describe how the bug manifests. -->
**Expected behavior:**
<!-- Describe what the behavior would be without the bug. -->
**Steps to reproduce:**
<!-- Please explain the steps required to duplicate the issue, especially if you are able to provide a sample application. -->
**Related code:**
<!-- If you are able to illustrate the bug or feature request with an example, please provide a sample application via one of the following means:
A sample application via GitHub
StackBlitz (https://stackblitz.com)
Ionic Angular StackBlitz: https://stackblitz.com/edit/ionic-v4-angular-tabs
Plunker (http://plnkr.co/edit/cpeRJs?p=preview)
-->
```
insert short code snippets here
```
**Other information:**
<!-- List any other information that is relevant to your issue. Stack traces, related issues, suggestions on how to fix, Stack Overflow links, forum links, etc. -->
**Ionic info:**
<!-- (run `ionic info` from a terminal/cmd prompt and paste output below): -->
```
insert the output from ionic info here
```

56
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,56 @@
name: 🐛 Bug Report
description: Create a report to help us improve Ionic Framework
title: 'bug: '
body:
- type: checkboxes
attributes:
label: Prequisites
description: Please ensure you have completed all of the following.
options:
- label: I have read the [Contributing Guidelines](https://github.com/ionic-team/ionic-framework/blob/main/.github/CONTRIBUTING.md#creating-an-issue).
required: true
- label: I agree to follow the [Code of Conduct](https://ionicframework.com/code-of-conduct).
required: true
- label: I have searched for [existing issues](https://github.com/ionic-team/ionic-framework/issues) that already report this problem, without success.
required: true
- type: checkboxes
attributes:
label: Ionic Framework Version
description: Please select which versions of Ionic Framework this issue impacts. For Ionic Framework 1.x issues, please use https://github.com/ionic-team/ionic-v1. For Ionic Framework 2.x and 3.x issues, please use https://github.com/ionic-team/ionic-v3.
options:
- label: v4.x
- label: v5.x
- label: v6.x
- type: textarea
attributes:
label: Current Behavior
description: A clear description of what the bug is and how it manifests.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: A clear description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Steps to Reproduce
description: Please explain the steps required to duplicate this issue.
validations:
required: true
- type: input
attributes:
label: Code Reproduction URL
description: Please reproduce this issue in a blank Ionic Framework starter application and provide a link to the repo. Try out our [Getting Started Wizard](https://ionicframework.com/start#basics) to quickly spin up an Ionic Framework starter app. This is the best way to ensure this issue is triaged quickly. Issues without a code reproduction may be closed if the Ionic Team cannot reproduce the issue you are reporting.
placeholder: https://github.com/...
- type: textarea
attributes:
label: Ionic Info
description: Please run `ionic info` from within your Ionic Framework project directory and paste the output below.
validations:
requred: true
- type: textarea
attributes:
label: Additional Information
description: List any other information that is relevant to your issue. Stack traces, related issues, suggestions on how to fix, Stack Overflow links, forum links, etc.

View File

@ -1,11 +0,0 @@
---
name: CLI
about: Suggest an improvement for the CLI
title: ''
labels: 'ionitron: cli'
assignees: ''
---
# CLI
Please do not submit bug reports or feature requests related to the Ionic CLI. Instead, please submit an issue to the [Ionic CLI Repository](https://github.com/ionic-team/ionic-cli/issues/new/choose).

10
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,10 @@
contact_links:
- name: 📚 Documentation
url: https://github.com/ionic-team/ionic-docs/issues/new/choose
about: This issue tracker is not for documentation issues. Please file documentation issues on the Ionic Docs repo.
- name: 💻 CLI
url: https://github.com/ionic-team/ionic-cli/issues/new/choose
about: This issue tracker is not for CLI issues. Please file CLI issues on the Ionic CLI repo.
- name: 🤔 Support Question
url: https://forum.ionicframework.com/
about: This issue tracker is not for support questions. Please post your question on the Ionic Forums.

View File

@ -1,11 +0,0 @@
---
name: Documentation
about: Suggest an improvement for the documentation of this project
title: ''
labels: 'ionitron: docs'
assignees: ''
---
# Documentation
Please do not submit issues on how to improve or fix the documentation. Instead, please submit an issue to the [Ionic Docs Repository](https://github.com/ionic-team/ionic-docs/issues/new/choose).

View File

@ -1,39 +0,0 @@
---
name: Feature Request
about: Suggest an idea for this project
title: 'feat: '
labels: ''
assignees: ''
---
<!-- Before submitting an issue, please consult our docs (https://ionicframework.com/docs/). -->
<!-- Please make sure you are posting an issue pertaining to the Ionic Framework. If you are having an issue with the Ionic Appflow services (Ionic View, Ionic Deploy, etc.) please consult the Ionic Appflow support portal (https://ionic.zendesk.com/hc/en-us) -->
<!-- Please do not submit support requests or "How to" questions here. Instead, please use one of these channels: https://forum.ionicframework.com/ or http://ionicworldwide.herokuapp.com/ -->
<!-- ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION. -->
# Feature Request
**Ionic version:**
<!-- (For Ionic 1.x issues, please use https://github.com/ionic-team/ionic-v1) -->
<!-- (For Ionic 2.x & 3.x issues, please use https://github.com/ionic-team/ionic-v3) -->
[ ] **4.x**
[ ] **5.x**
[ ] **6.x**
**Describe the Feature Request**
<!-- A clear and concise description of what the feature request is. Please include if your feature request is related to a problem. -->
**Describe Preferred Solution**
<!-- A clear and concise description of what you want to happen. -->
**Describe Alternatives**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
**Related Code**
<!-- If you are able to illustrate the feature request with an example, please provide a sample application via an online code collaborator such as [StackBlitz](https://stackblitz.com), or [GitHub](https://github.com). -->
**Additional Context**
<!-- List any other information that is relevant to your issue. Stack traces, related issues, suggestions on how to add, use case, Stack Overflow links, forum links, screenshots, OS if applicable, etc. -->

View File

@ -0,0 +1,43 @@
name: 💡 Feature Request
description: Suggest an idea for Ionic Framework
title: 'feat: '
body:
- type: checkboxes
attributes:
label: Prequisites
description: Please ensure you have completed all of the following.
options:
- label: I have read the [Contributing Guidelines](https://github.com/ionic-team/ionic-framework/blob/main/.github/CONTRIBUTING.md#creating-an-issue).
required: true
- label: I agree to follow the [Code of Conduct](https://ionicframework.com/code-of-conduct).
required: true
- label: I have searched for [existing issues](https://github.com/ionic-team/ionic-framework/issues) that already include this feature request, without success.
required: true
- type: textarea
attributes:
label: Describe the Feature Request
description: A clear and concise description of what the feature does.
validations:
required: true
- type: textarea
attributes:
label: Describe the Use Case
description: A clear and concise use case for what problem this feature would solve.
validations:
required: true
- type: textarea
attributes:
label: Describe Preferred Solution
description: A clear and concise description of what you how you want this feature to be added to Ionic Framework.
- type: textarea
attributes:
label: Describe Alternatives
description: A clear and concise description of any alternative solutions or features you have considered.
- type: textarea
attributes:
label: Related Code
description: If you are able to illustrate the feature request with an example, please provide a sample Ionic Framework application. Try out our [Getting Started Wizard](https://ionicframework.com/start#basics) to quickly spin up an Ionic Framework starter app.
- type: textarea
attributes:
label: Additional Information
description: List any other information that is relevant to your issue. Stack traces, related issues, suggestions on how to implement, Stack Overflow links, forum links, etc.

View File

@ -1,11 +0,0 @@
---
name: Support Question
about: Question on how to use this project
title: 'support: '
labels: 'ionitron: support'
assignees: ''
---
# Support Question
Please do not submit support requests or "How to" questions here. Instead, please use the Ionic Forum: https://forum.ionicframework.com/

View File

@ -46,4 +46,44 @@ export { IonicModule } from './ionic-module';
export { IonicSafeString, getPlatforms, isPlatform, createAnimation, IonicSwiper } from '@ionic/core'; export { IonicSafeString, getPlatforms, isPlatform, createAnimation, IonicSwiper } from '@ionic/core';
// CORE TYPES // CORE TYPES
export { Animation, AnimationBuilder, AnimationCallbackOptions, AnimationDirection, AnimationFill, AnimationKeyFrames, AnimationLifecycle, Gesture, GestureConfig, GestureDetail, mdTransitionAnimation, iosTransitionAnimation, NavComponentWithProps } from '@ionic/core'; export {
Animation,
AnimationBuilder,
AnimationCallbackOptions,
AnimationDirection,
AnimationFill,
AnimationKeyFrames,
AnimationLifecycle,
Gesture,
GestureConfig,
GestureDetail,
mdTransitionAnimation,
iosTransitionAnimation,
NavComponentWithProps,
SpinnerTypes,
ActionSheetOptions,
ActionSheetButton,
AlertOptions,
AlertInput,
AlertTextareaAttributes,
AlertInputAttributes,
AlertButton,
LoadingOptions,
ModalOptions,
PickerOptions,
PickerButton,
PickerColumn,
PickerColumnOption,
PopoverOptions,
ToastOptions,
ToastButton
} from '@ionic/core';

View File

@ -254,7 +254,7 @@ rules:
- visibility - visibility
- z-index - z-index
property-blacklist: property-disallowed-list:
- background-position - background-position
- right - right
- left - left

View File

@ -110,10 +110,16 @@
text-align: $action-sheet-ios-text-align; text-align: $action-sheet-ios-text-align;
} }
.action-sheet-title.action-sheet-has-sub-title {
font-weight: $action-sheet-ios-title-with-sub-title-font-weight;
}
.action-sheet-sub-title { .action-sheet-sub-title {
@include padding($action-sheet-ios-sub-title-padding-top, $action-sheet-ios-sub-title-padding-end, $action-sheet-ios-sub-title-padding-bottom, $action-sheet-ios-sub-title-padding-start); @include padding($action-sheet-ios-sub-title-padding-top, $action-sheet-ios-sub-title-padding-end, $action-sheet-ios-sub-title-padding-bottom, $action-sheet-ios-sub-title-padding-start);
font-size: $action-sheet-ios-sub-title-font-size; font-size: $action-sheet-ios-sub-title-font-size;
font-weight: $action-sheet-ios-title-font-weight;
} }

View File

@ -55,6 +55,9 @@ $action-sheet-ios-title-font-size: 13px !default;
/// @prop - Font weight of the action sheet title /// @prop - Font weight of the action sheet title
$action-sheet-ios-title-font-weight: 400 !default; $action-sheet-ios-title-font-weight: 400 !default;
/// @prop - Font weight of the action sheet title when it has a sub title
$action-sheet-ios-title-with-sub-title-font-weight: 600 !default;
/// @prop - Border width of the action sheet title /// @prop - Border width of the action sheet title
$action-sheet-ios-title-border-width: $hairlines-width !default; $action-sheet-ios-title-border-width: $hairlines-width !default;
@ -72,10 +75,10 @@ $action-sheet-ios-title-border-color: rgba($text-col
// -------------------------------------------------- // --------------------------------------------------
/// @prop - Font size of the action sheet sub title /// @prop - Font size of the action sheet sub title
$action-sheet-ios-sub-title-font-size: 12px !default; $action-sheet-ios-sub-title-font-size: 13px !default;
/// @prop - Padding top of the action sheet sub title /// @prop - Padding top of the action sheet sub title
$action-sheet-ios-sub-title-padding-top: 15px !default; $action-sheet-ios-sub-title-padding-top: 6px !default;
/// @prop - Padding end of the action sheet sub title /// @prop - Padding end of the action sheet sub title
$action-sheet-ios-sub-title-padding-end: 0 !default; $action-sheet-ios-sub-title-padding-end: 0 !default;
@ -103,7 +106,7 @@ $action-sheet-ios-button-text-color: ion-color(prim
$action-sheet-ios-button-icon-font-size: 28px !default; $action-sheet-ios-button-icon-font-size: 28px !default;
/// @prop - Padding right of the action sheet button icon /// @prop - Padding right of the action sheet button icon
$action-sheet-ios-button-icon-padding-right: .1em !default; $action-sheet-ios-button-icon-padding-right: .3em !default;
/// @prop - Font size of the action sheet button /// @prop - Font size of the action sheet button
$action-sheet-ios-button-font-size: 20px !default; $action-sheet-ios-button-font-size: 20px !default;

View File

@ -258,7 +258,10 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
<div class="action-sheet-container"> <div class="action-sheet-container">
<div class="action-sheet-group" ref={el => this.groupEl = el}> <div class="action-sheet-group" ref={el => this.groupEl = el}>
{this.header !== undefined && {this.header !== undefined &&
<div class="action-sheet-title"> <div class={{
'action-sheet-title': true,
'action-sheet-has-sub-title': this.subHeader !== undefined
}}>
{this.header} {this.header}
{this.subHeader && <div class="action-sheet-sub-title">{this.subHeader}</div>} {this.subHeader && <div class="action-sheet-sub-title">{this.subHeader}</div>}
</div> </div>

View File

@ -34,6 +34,39 @@ Any of the defined [CSS Custom Properties](#css-custom-properties) can be used t
> If you are building an Ionic Angular app, the styles need to be added to a global stylesheet file. Read [Style Placement](#style-placement) in the Angular section below for more information. > If you are building an Ionic Angular app, the styles need to be added to a global stylesheet file. Read [Style Placement](#style-placement) in the Angular section below for more information.
## Interfaces
### ActionSheetButton
```typescript
interface ActionSheetButton {
text?: string;
role?: 'cancel' | 'destructive' | 'selected' | string;
icon?: string;
cssClass?: string | string[];
handler?: () => boolean | void | Promise<boolean | void>;
}
```
### ActionSheetOptions
```typescript
interface ActionSheetOptions {
header?: string;
subHeader?: string;
cssClass?: string | string[];
buttons: (ActionSheetButton | string)[];
backdropDismiss?: boolean;
translucent?: boolean;
animated?: boolean;
mode?: Mode;
keyboardClose?: boolean;
id?: string;
enterAnimation?: AnimationBuilder;
leaveAnimation?: AnimationBuilder;
}
```
<!-- Auto Generated Below --> <!-- Auto Generated Below -->

View File

@ -41,6 +41,76 @@ Any of the defined [CSS Custom Properties](#css-custom-properties) can be used t
> If you are building an Ionic Angular app, the styles need to be added to a global stylesheet file. Read [Style Placement](#style-placement) in the Angular section below for more information. > If you are building an Ionic Angular app, the styles need to be added to a global stylesheet file. Read [Style Placement](#style-placement) in the Angular section below for more information.
## Interfaces
### AlertButton
```typescript
interface AlertButton {
text: string;
role?: string;
cssClass?: string | string[];
handler?: (value: any) => boolean | void | {[key: string]: any};
}
```
### AlertInput
```typescript
interface AlertInput {
type?: TextFieldTypes | 'checkbox' | 'radio' | 'textarea';
name?: string;
placeholder?: string;
value?: any;
label?: string;
checked?: boolean;
disabled?: boolean;
id?: string;
handler?: (input: AlertInput) => void;
min?: string | number;
max?: string | number;
cssClass?: string | string[];
attributes?: AlertInputAttributes | AlertTextareaAttributes;
tabindex?: number;
}
```
### AlertInputAttributes
```typescript
interface AlertInputAttributes extends JSXBase.InputHTMLAttributes<HTMLInputElement> {}
```
### AlertOptions
```typescript
interface AlertOptions {
header?: string;
subHeader?: string;
message?: string | IonicSafeString;
cssClass?: string | string[];
inputs?: AlertInput[];
buttons?: (AlertButton | string)[];
backdropDismiss?: boolean;
translucent?: boolean;
animated?: boolean;
mode?: Mode;
keyboardClose?: boolean;
id?: string;
enterAnimation?: AnimationBuilder;
leaveAnimation?: AnimationBuilder;
}
```
### AlertTextareaAttributes
```typescript
interface AlertTextareaAttributes extends JSXBase.TextareaHTMLAttributes<HTMLTextAreaElement> {}
```
<!-- Auto Generated Below --> <!-- Auto Generated Below -->

View File

@ -62,7 +62,6 @@
user-select: none; user-select: none;
vertical-align: top; // the better option for most scenarios vertical-align: top; // the better option for most scenarios
vertical-align: -webkit-baseline-middle; // the best for those that support it vertical-align: -webkit-baseline-middle; // the best for those that support it
pointer-events: auto;
font-kerning: none; font-kerning: none;
} }

View File

@ -146,9 +146,9 @@
display: none; display: none;
position: absolute; position: absolute;
/* stylelint-disable property-blacklist */ /* stylelint-disable property-disallowed-list */
left: -100%; left: -100%;
/* stylelint-enable property-blacklist */ /* stylelint-enable property-disallowed-list */
width: 100%; width: 100%;
height: 100vh; height: 100vh;
@ -161,9 +161,9 @@
.transition-cover { .transition-cover {
position: absolute; position: absolute;
/* stylelint-disable property-blacklist */ /* stylelint-disable property-disallowed-list */
right: 0; right: 0;
/* stylelint-enable property-blacklist */ /* stylelint-enable property-disallowed-list */
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -177,9 +177,9 @@
display: block; display: block;
position: absolute; position: absolute;
/* stylelint-disable property-blacklist */ /* stylelint-disable property-disallowed-list */
right: 0; right: 0;
/* stylelint-enable property-blacklist */ /* stylelint-enable property-disallowed-list */
width: 10px; width: 10px;
height: 100%; height: 100%;

View File

@ -5,10 +5,10 @@
ion-item-options { ion-item-options {
@include multi-dir() { @include multi-dir() {
/* stylelint-disable property-blacklist */ /* stylelint-disable property-disallowed-list */
top: 0; top: 0;
right: 0; right: 0;
/* stylelint-enable property-blacklist */ /* stylelint-enable property-disallowed-list */
} }
@include ltr() { @include ltr() {
@ -19,10 +19,10 @@ ion-item-options {
justify-content: flex-start; justify-content: flex-start;
&:not(.item-options-end) { &:not(.item-options-end) {
/* stylelint-disable property-blacklist */ /* stylelint-disable property-disallowed-list */
right: auto; right: auto;
left: 0; left: 0;
/* stylelint-enable property-blacklist */ /* stylelint-enable property-disallowed-list */
justify-content: flex-end; justify-content: flex-end;
} }
@ -41,10 +41,10 @@ ion-item-options {
.item-options-start { .item-options-start {
@include multi-dir() { @include multi-dir() {
/* stylelint-disable property-blacklist */ /* stylelint-disable property-disallowed-list */
right: auto; right: auto;
left: 0; left: 0;
/* stylelint-enable property-blacklist */ /* stylelint-enable property-disallowed-list */
} }
@include ltr() { @include ltr() {

View File

@ -32,7 +32,7 @@ ion-item-sliding .item {
.item-sliding-active-swipe-end .item-options-end .item-option-expandable { .item-sliding-active-swipe-end .item-options-end .item-option-expandable {
@include multi-dir() { @include multi-dir() {
/* stylelint-disable-next-line property-blacklist */ /* stylelint-disable-next-line property-disallowed-list */
padding-left: 100%; padding-left: 100%;
} }
@ -50,7 +50,7 @@ ion-item-sliding .item {
.item-sliding-active-swipe-start .item-options-start .item-option-expandable { .item-sliding-active-swipe-start .item-options-start .item-option-expandable {
@include multi-dir() { @include multi-dir() {
/* stylelint-disable-next-line property-blacklist */ /* stylelint-disable-next-line property-disallowed-list */
padding-right: 100%; padding-right: 100%;
} }

View File

@ -299,7 +299,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
render() { render() {
const { counterString, detail, detailIcon, download, fill, labelColorStyles, lines, disabled, href, rel, shape, target, routerAnimation, routerDirection } = this; const { counterString, detail, detailIcon, download, fill, labelColorStyles, lines, disabled, href, rel, shape, target, routerAnimation, routerDirection } = this;
const childStyles = {}; const childStyles = {} as any;
const mode = getIonMode(this); const mode = getIonMode(this);
const clickable = this.isClickable(); const clickable = this.isClickable();
const canActivate = this.canActivate(); const canActivate = this.canActivate();
@ -322,10 +322,11 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
this.itemStyles.forEach(value => { this.itemStyles.forEach(value => {
Object.assign(childStyles, value); Object.assign(childStyles, value);
}); });
const ariaDisabled = (disabled || childStyles['item-interactive-disabled']) ? 'true' : null;
return ( return (
<Host <Host
aria-disabled={disabled ? 'true' : null} aria-disabled={ariaDisabled}
class={{ class={{
...childStyles, ...childStyles,
...labelColorStyles, ...labelColorStyles,

View File

@ -0,0 +1,11 @@
import { newE2EPage } from '@stencil/core/testing';
import { AxePuppeteer } from '@axe-core/puppeteer';
test('item: axe', async () => {
const page = await newE2EPage({
url: '/src/components/item/test/a11y?ionic:_testing=true'
});
const results = await new AxePuppeteer(page).analyze();
expect(results.violations.length).toEqual(0);
});

View File

@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8">
<title>Item - 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>Item</h1>
<ion-item>
<ion-label>Item with Input</ion-label>
<ion-input placeholder="Placeholder"></ion-input>
</ion-item>
<ion-item disabled>
<ion-label>Item disabled with Input</ion-label>
<ion-input placeholder="Placeholder"></ion-input>
</ion-item>
<ion-item>
<ion-label>Item with Input disabled</ion-label>
<ion-input placeholder="Placeholder" disabled></ion-input>
</ion-item>
<ion-item>
<ion-label position="floating">Item with Select</ion-label>
<ion-select>
<ion-select-option value="">No Game Console</ion-select-option>
<ion-select-option value="nes">NES</ion-select-option>
<ion-select-option value="n64" selected>Nintendo64</ion-select-option>
<ion-select-option value="ps">PlayStation</ion-select-option>
<ion-select-option value="genesis">Sega Genesis</ion-select-option>
<ion-select-option value="saturn">Sega Saturn</ion-select-option>
<ion-select-option value="snes">SNES</ion-select-option>
</ion-select>
</ion-item>
<ion-item disabled>
<ion-label position="floating">Item disabled with Select</ion-label>
<ion-select>
<ion-select-option value="">No Game Console</ion-select-option>
<ion-select-option value="nes">NES</ion-select-option>
<ion-select-option value="n64" selected>Nintendo64</ion-select-option>
<ion-select-option value="ps">PlayStation</ion-select-option>
<ion-select-option value="genesis">Sega Genesis</ion-select-option>
<ion-select-option value="saturn">Sega Saturn</ion-select-option>
<ion-select-option value="snes">SNES</ion-select-option>
</ion-select>
</ion-item>
<ion-item>
<ion-label position="floating">Item with Select disabled</ion-label>
<ion-select disabled>
<ion-select-option value="">No Game Console</ion-select-option>
<ion-select-option value="nes">NES</ion-select-option>
<ion-select-option value="n64" selected>Nintendo64</ion-select-option>
<ion-select-option value="ps">PlayStation</ion-select-option>
<ion-select-option value="genesis">Sega Genesis</ion-select-option>
<ion-select-option value="saturn">Sega Saturn</ion-select-option>
<ion-select-option value="snes">SNES</ion-select-option>
</ion-select>
</ion-item>
<ion-item>
<ion-label>Item with Toggle</ion-label>
<ion-toggle slot="end"></ion-toggle>
</ion-item>
<ion-item disabled>
<ion-label>Item disabled with Toggle</ion-label>
<ion-toggle slot="end"></ion-toggle>
</ion-item>
<ion-item>
<ion-label>Item with Toggle disabled</ion-label>
<ion-toggle slot="end" disabled></ion-toggle>
</ion-item>
<ion-item>
<ion-label>Item with Radio</ion-label>
<ion-radio slot="start" value="biff"></ion-radio>
</ion-item>
<ion-item disabled>
<ion-label>Item disabled with Radio</ion-label>
<ion-radio slot="start" value="biff"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Item with Radio disabled</ion-label>
<ion-radio slot="start" value="biff" disabled></ion-radio>
</ion-item>
</main>
</body>
</html>

View File

@ -37,6 +37,28 @@ Any of the defined [CSS Custom Properties](#css-custom-properties) can be used t
> If you are building an Ionic Angular app, the styles need to be added to a global stylesheet file. Read [Style Placement](#style-placement) in the Angular section below for more information. > If you are building an Ionic Angular app, the styles need to be added to a global stylesheet file. Read [Style Placement](#style-placement) in the Angular section below for more information.
## Interfaces
### LoadingOptions
```typescript
interface LoadingOptions {
spinner?: SpinnerTypes | null;
message?: string | IonicSafeString;
cssClass?: string | string[];
showBackdrop?: boolean;
duration?: number;
translucent?: boolean;
animated?: boolean;
backdropDismiss?: boolean;
mode?: Mode;
keyboardClose?: boolean;
id?: string;
enterAnimation?: AnimationBuilder;
leaveAnimation?: AnimationBuilder;
}
```
<!-- Auto Generated Below --> <!-- Auto Generated Below -->
@ -139,17 +161,23 @@ import { IonButton, IonContent, IonPage, useIonLoading } from '@ionic/react';
interface LoadingProps {} interface LoadingProps {}
const LoadingExample: React.FC<LoadingProps> = () => { const LoadingExample: React.FC<LoadingProps> = () => {
const [present] = useIonLoading(); const [present, dismiss] = useIonLoading();
/**
* The recommended way of dismissing is to use the `dismiss` property
* on `IonLoading`, but the `dismiss` method returned from `useIonLoading`
* can be used for more complex scenarios.
*/
return ( return (
<IonPage> <IonPage>
<IonContent> <IonContent>
<IonButton <IonButton
expand="block" expand="block"
onClick={() => onClick={() => {
present({ present({
duration: 3000, message: 'Loading...',
duration: 3000
}) })
} }}
> >
Show Loading Show Loading
</IonButton> </IonButton>

View File

@ -7,17 +7,23 @@ import { IonButton, IonContent, IonPage, useIonLoading } from '@ionic/react';
interface LoadingProps {} interface LoadingProps {}
const LoadingExample: React.FC<LoadingProps> = () => { const LoadingExample: React.FC<LoadingProps> = () => {
const [present] = useIonLoading(); const [present, dismiss] = useIonLoading();
/**
* The recommended way of dismissing is to use the `dismiss` property
* on `IonLoading`, but the `dismiss` method returned from `useIonLoading`
* can be used for more complex scenarios.
*/
return ( return (
<IonPage> <IonPage>
<IonContent> <IonContent>
<IonButton <IonButton
expand="block" expand="block"
onClick={() => onClick={() => {
present({ present({
duration: 3000, message: 'Loading...',
duration: 3000
}) })
} }}
> >
Show Loading Show Loading
</IonButton> </IonButton>

View File

@ -4,6 +4,7 @@ import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global'; import { getIonMode } from '../../global/ionic-global';
import { Color } from '../../interface'; import { Color } from '../../interface';
import { ButtonInterface } from '../../utils/element-interface'; import { ButtonInterface } from '../../utils/element-interface';
import { inheritAttributes } from '../../utils/helpers';
import { menuController } from '../../utils/menu-controller'; import { menuController } from '../../utils/menu-controller';
import { createColorClasses, hostContext } from '../../utils/theme'; import { createColorClasses, hostContext } from '../../utils/theme';
import { updateVisibility } from '../menu-toggle/menu-toggle-util'; import { updateVisibility } from '../menu-toggle/menu-toggle-util';
@ -23,6 +24,8 @@ import { updateVisibility } from '../menu-toggle/menu-toggle-util';
shadow: true shadow: true
}) })
export class MenuButton implements ComponentInterface, ButtonInterface { export class MenuButton implements ComponentInterface, ButtonInterface {
private inheritedAttributes: { [k: string]: any } = {};
@Element() el!: HTMLIonSegmentElement; @Element() el!: HTMLIonSegmentElement;
@State() visible = false; @State() visible = false;
@ -54,6 +57,10 @@ export class MenuButton implements ComponentInterface, ButtonInterface {
*/ */
@Prop() type: 'submit' | 'reset' | 'button' = 'button'; @Prop() type: 'submit' | 'reset' | 'button' = 'button';
componentWillLoad() {
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
}
componentDidLoad() { componentDidLoad() {
this.visibilityChanged(); this.visibilityChanged();
} }
@ -69,7 +76,7 @@ export class MenuButton implements ComponentInterface, ButtonInterface {
} }
render() { render() {
const { color, disabled } = this; const { color, disabled, inheritedAttributes } = this;
const mode = getIonMode(this); const mode = getIonMode(this);
const menuIcon = config.get('menuIcon', mode === 'ios' ? 'menu-outline' : 'menu-sharp'); const menuIcon = config.get('menuIcon', mode === 'ios' ? 'menu-outline' : 'menu-sharp');
const hidden = this.autoHide && !this.visible; const hidden = this.autoHide && !this.visible;
@ -78,6 +85,8 @@ export class MenuButton implements ComponentInterface, ButtonInterface {
type: this.type type: this.type
}; };
const ariaLabel = inheritedAttributes['aria-label'] || 'menu';
return ( return (
<Host <Host
onClick={this.onClick} onClick={this.onClick}
@ -99,7 +108,7 @@ export class MenuButton implements ComponentInterface, ButtonInterface {
disabled={disabled} disabled={disabled}
class="button-native" class="button-native"
part="native" part="native"
aria-label="menu" aria-label={ariaLabel}
> >
<span class="button-inner"> <span class="button-inner">
<slot> <slot>

View File

@ -0,0 +1,11 @@
import { newE2EPage } from '@stencil/core/testing';
import { AxePuppeteer } from '@axe-core/puppeteer';
test('menu-button: axe', async () => {
const page = await newE2EPage({
url: '/src/components/menu-button/test/a11y?ionic:_testing=true'
});
const results = await new AxePuppeteer(page).analyze();
expect(results.violations.length).toEqual(0);
});

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8">
<title>Menu Button - 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 type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
<main>
<h1>Menu Button</h1>
<ion-menu-button auto-hide="false"></ion-menu-button>
<ion-menu-button auto-hide="false" aria-label="Custom Label"></ion-menu-button>
</main>
</body>
</html>

View File

@ -29,6 +29,7 @@
<ion-menu-button auto-hide="false" color="secondary" class="custom ion-focused"></ion-menu-button> <ion-menu-button auto-hide="false" color="secondary" class="custom ion-focused"></ion-menu-button>
<ion-menu-button auto-hide="false" class="custom-large"></ion-menu-button> <ion-menu-button auto-hide="false" class="custom-large"></ion-menu-button>
<ion-menu-button auto-hide="false" class="custom-large ion-focused"></ion-menu-button> <ion-menu-button auto-hide="false" class="custom-large ion-focused"></ion-menu-button>
<ion-menu-button auto-hide="false" aria-label="My Custom Menu Button Label"></ion-menu-button>
<h1>Colors</h1> <h1>Colors</h1>
<ion-menu-button auto-hide="false" color="primary"></ion-menu-button> <ion-menu-button auto-hide="false" color="primary"></ion-menu-button>

View File

@ -63,7 +63,7 @@
--ion-safe-area-right: 0px; --ion-safe-area-right: 0px;
@include multi-dir() { @include multi-dir() {
/* stylelint-disable property-blacklist */ /* stylelint-disable property-disallowed-list */
right: auto; right: auto;
left: 0; left: 0;
} }
@ -75,7 +75,7 @@
@include multi-dir() { @include multi-dir() {
right: 0; right: 0;
left: auto; left: auto;
/* stylelint-enable property-blacklist */ /* stylelint-enable property-disallowed-list */
} }
} }

View File

@ -102,6 +102,30 @@ ion-modal.stack-modal {
} }
``` ```
## Interfaces
### ModalOptions
```typescript
interface ModalOptions<T extends ComponentRef = ComponentRef> {
component: T;
componentProps?: ComponentProps<T>;
presentingElement?: HTMLElement;
showBackdrop?: boolean;
backdropDismiss?: boolean;
cssClass?: string | string[];
animated?: boolean;
swipeToClose?: boolean;
mode?: Mode;
keyboardClose?: boolean;
id?: string;
enterAnimation?: AnimationBuilder;
leaveAnimation?: AnimationBuilder;
}
```
<!-- Auto Generated Below --> <!-- Auto Generated Below -->
@ -549,6 +573,7 @@ In most scenarios, setting a ref on `IonRouterOutlet` and passing that ref's `cu
isOpen={show2ndModal} isOpen={show2ndModal}
cssClass='my-custom-class' cssClass='my-custom-class'
presentingElement={firstModalRef.current} presentingElement={firstModalRef.current}
swipeToClose={true}
onDidDismiss={() => setShow2ndModal(false)}> onDidDismiss={() => setShow2ndModal(false)}>
<p>This is more modal content</p> <p>This is more modal content</p>
<IonButton onClick={() => setShow2ndModal(false)}>Close Modal</IonButton> <IonButton onClick={() => setShow2ndModal(false)}>Close Modal</IonButton>
@ -811,6 +836,47 @@ export default defineComponent({
> If you need a wrapper element inside of your modal component, we recommend using an `<ion-page>` so that the component dimensions are still computed properly. > If you need a wrapper element inside of your modal component, we recommend using an `<ion-page>` so that the component dimensions are still computed properly.
### Swipeable Modals
Modals in iOS mode have the ability to be presented in a card-style and swiped to close. The card-style presentation and swipe to close gesture are not mutually exclusive, meaning you can pick and choose which features you want to use. For example, you can have a card-style modal that cannot be swiped or a full sized modal that can be swiped.
> Card style modals when running on iPhone-sized devices do not have backdrops. As a result, the `--backdrop-opacity` variable will not have any effect.
```html
<template>
<ion-page>
<ion-content>
<ion-button @click="setOpen(true)">Show Modal</ion-button>
<ion-modal
:is-open="isOpenRef"
css-class="my-custom-class"
:swipe-to-close="true"
:presenting-element="$parent.$refs.ionRouterOutlet"
@didDismiss="setOpen(false)"
>
<Modal :data="data"></Modal>
</ion-modal>
</ion-content>
</ion-page>
</template>
<script lang="ts">
import { IonModal, IonButton, IonContent, IonPage } from '@ionic/vue';
import { defineComponent, ref } from 'vue';
import Modal from './modal.vue'
export default defineComponent({
components: { IonModal, IonButton, Modal, IonContent, IonPage },
setup() {
const isOpenRef = ref(false);
const setOpen = (state: boolean) => isOpenRef.value = state;
const data = { content: 'New Content' };
return { isOpenRef, setOpen, data }
}
});
</script>
```
## Properties ## Properties

View File

@ -147,6 +147,7 @@ In most scenarios, setting a ref on `IonRouterOutlet` and passing that ref's `cu
isOpen={show2ndModal} isOpen={show2ndModal}
cssClass='my-custom-class' cssClass='my-custom-class'
presentingElement={firstModalRef.current} presentingElement={firstModalRef.current}
swipeToClose={true}
onDidDismiss={() => setShow2ndModal(false)}> onDidDismiss={() => setShow2ndModal(false)}>
<p>This is more modal content</p> <p>This is more modal content</p>
<IonButton onClick={() => setShow2ndModal(false)}>Close Modal</IonButton> <IonButton onClick={() => setShow2ndModal(false)}>Close Modal</IonButton>

View File

@ -93,3 +93,44 @@ export default defineComponent({
``` ```
> If you need a wrapper element inside of your modal component, we recommend using an `<ion-page>` so that the component dimensions are still computed properly. > If you need a wrapper element inside of your modal component, we recommend using an `<ion-page>` so that the component dimensions are still computed properly.
### Swipeable Modals
Modals in iOS mode have the ability to be presented in a card-style and swiped to close. The card-style presentation and swipe to close gesture are not mutually exclusive, meaning you can pick and choose which features you want to use. For example, you can have a card-style modal that cannot be swiped or a full sized modal that can be swiped.
> Card style modals when running on iPhone-sized devices do not have backdrops. As a result, the `--backdrop-opacity` variable will not have any effect.
```html
<template>
<ion-page>
<ion-content>
<ion-button @click="setOpen(true)">Show Modal</ion-button>
<ion-modal
:is-open="isOpenRef"
css-class="my-custom-class"
:swipe-to-close="true"
:presenting-element="$parent.$refs.ionRouterOutlet"
@didDismiss="setOpen(false)"
>
<Modal :data="data"></Modal>
</ion-modal>
</ion-content>
</ion-page>
</template>
<script lang="ts">
import { IonModal, IonButton, IonContent, IonPage } from '@ionic/vue';
import { defineComponent, ref } from 'vue';
import Modal from './modal.vue'
export default defineComponent({
components: { IonModal, IonButton, Modal, IonContent, IonPage },
setup() {
const isOpenRef = ref(false);
const setOpen = (state: boolean) => isOpenRef.value = state;
const data = { content: 'New Content' };
return { isOpenRef, setOpen, data }
}
});
</script>
```

View File

@ -169,7 +169,7 @@ describe('NavController', () => {
describe('insert', () => { describe('insert', () => {
it('should insert at the begining with no async transition', async () => { it('should insert at the beginning with no async transition', async () => {
const view4 = mockView(MockView4); const view4 = mockView(MockView4);
const instance4 = spyOnLifecycles(view4); const instance4 = spyOnLifecycles(view4);
const opts: NavOptions = {}; const opts: NavOptions = {};

View File

@ -2,7 +2,71 @@
A Picker is a dialog that displays a row of buttons and columns underneath. It appears on top of the app's content, and at the bottom of the viewport. A Picker is a dialog that displays a row of buttons and columns underneath. It appears on top of the app's content, and at the bottom of the viewport.
## Interfaces
### PickerButton
```typescript
interface PickerButton {
text?: string;
role?: string;
cssClass?: string | string[];
handler?: (value: any) => boolean | void;
}
```
### PickerColumn
```typescript
interface PickerColumn {
name: string;
align?: string;
selectedIndex?: number;
prevSelected?: number;
prefix?: string;
suffix?: string;
options: PickerColumnOption[];
cssClass?: string | string[];
columnWidth?: string;
prefixWidth?: string;
suffixWidth?: string;
optionsWidth?: string;
refresh?: () => void;
}
```
### PickerColumnOption
```typescript
interface PickerColumnOption {
text?: string;
value?: any;
disabled?: boolean;
duration?: number;
transform?: string;
selected?: boolean;
}
```
### PickerOptions
```typescript
interface PickerOptions {
columns: PickerColumn[];
buttons?: PickerButton[];
cssClass?: string | string[];
showBackdrop?: boolean;
backdropDismiss?: boolean;
animated?: boolean;
mode?: Mode;
keyboardClose?: boolean;
id?: string;
enterAnimation?: AnimationBuilder;
leaveAnimation?: AnimationBuilder;
}
```
<!-- Auto Generated Below --> <!-- Auto Generated Below -->

View File

@ -36,47 +36,6 @@ If you need fine grained control over when the popover is presented and dismisse
We typically recommend that you write your popovers inline as it streamlines the amount of code in your application. You should only use the `popoverController` for complex use cases where writing a popover inline is impractical. When using a controller, your popover is not created ahead of time, so properties such as `trigger` and `trigger-action` are not applicable here. In addition, nested popovers are not compatible with the controller approach because the popover is automatically added to the root of your application when the `create` method is called. We typically recommend that you write your popovers inline as it streamlines the amount of code in your application. You should only use the `popoverController` for complex use cases where writing a popover inline is impractical. When using a controller, your popover is not created ahead of time, so properties such as `trigger` and `trigger-action` are not applicable here. In addition, nested popovers are not compatible with the controller approach because the popover is automatically added to the root of your application when the `create` method is called.
## Interfaces
Below you will find all of the options available to you when using the `popoverController`. These options should be supplied when calling `popoverController.create()`.
```typescript
interface PopoverOptions {
component: any;
componentProps?: { [key: string]: any };
showBackdrop?: boolean;
backdropDismiss?: boolean;
translucent?: boolean;
cssClass?: string | string[];
event?: Event;
animated?: boolean;
mode?: 'ios' | 'md';
keyboardClose?: boolean;
id?: string;
enterAnimation?: AnimationBuilder;
leaveAnimation?: AnimationBuilder;
size?: PopoverSize;
dismissOnSelect?: boolean;
reference?: PositionReference;
side?: PositionSide;
align?: PositionAlign;
}
```
## Types
Below you will find all of the custom types for `ion-popover`:
```typescript
type PopoverSize = 'cover' | 'auto';
type TriggerAction = 'click' | 'hover' | 'context-menu';
type PositionReference = 'trigger' | 'event';
type PositionSide = 'top' | 'right' | 'bottom' | 'left' | 'start' | 'end';
type PositionAlign = 'start' | 'center' | 'end';
```
## Customization ## Customization
@ -155,6 +114,48 @@ You can use the `dismissOnSelect` property to automatically close the popover wh
> Nested popovers cannot be created when using the `popoverController` because the popover is automatically added to the root of your application when the `create` method is called. > Nested popovers cannot be created when using the `popoverController` because the popover is automatically added to the root of your application when the `create` method is called.
## Interfaces
Below you will find all of the options available to you when using the `popoverController`. These options should be supplied when calling `popoverController.create()`.
```typescript
interface PopoverOptions {
component: any;
componentProps?: { [key: string]: any };
showBackdrop?: boolean;
backdropDismiss?: boolean;
translucent?: boolean;
cssClass?: string | string[];
event?: Event;
animated?: boolean;
mode?: 'ios' | 'md';
keyboardClose?: boolean;
id?: string;
enterAnimation?: AnimationBuilder;
leaveAnimation?: AnimationBuilder;
size?: PopoverSize;
dismissOnSelect?: boolean;
reference?: PositionReference;
side?: PositionSide;
align?: PositionAlign;
}
```
## Types
Below you will find all of the custom types for `ion-popover`:
```typescript
type PopoverSize = 'cover' | 'auto';
type TriggerAction = 'click' | 'hover' | 'context-menu';
type PositionReference = 'trigger' | 'event';
type PositionSide = 'top' | 'right' | 'bottom' | 'left' | 'start' | 'end';
type PositionAlign = 'start' | 'center' | 'end';
```
## Accessibility ## Accessibility
### Keyboard Navigation ### Keyboard Navigation

View File

@ -46,10 +46,10 @@
// Extend a bit to overflow. The size of animated distance. // Extend a bit to overflow. The size of animated distance.
.buffer-circles { .buffer-circles {
/* stylelint-disable property-blacklist */ /* stylelint-disable property-disallowed-list */
right: -10px; right: -10px;
left: -10px; left: -10px;
/* stylelint-enable property-blacklist */ /* stylelint-enable property-disallowed-list */
} }
// Determinate progress bar // Determinate progress bar
@ -58,7 +58,7 @@
.progress, .progress,
.progress-buffer-bar, .progress-buffer-bar,
.buffer-circles-container { .buffer-circles-container {
/* stylelint-disable-next-line property-blacklist */ /* stylelint-disable-next-line property-disallowed-list */
transform-origin: left top; transform-origin: left top;
transition: transform 150ms linear; transition: transform 150ms linear;
@ -88,12 +88,12 @@
// -------------------------------------------------- // --------------------------------------------------
.indeterminate-bar-primary { .indeterminate-bar-primary {
/* stylelint-disable property-blacklist */ /* stylelint-disable property-disallowed-list */
top: 0; top: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
left: -145.166611%; left: -145.166611%;
/* stylelint-enable property-blacklist */ /* stylelint-enable property-disallowed-list */
animation: primary-indeterminate-translate 2s infinite linear; animation: primary-indeterminate-translate 2s infinite linear;
@ -104,12 +104,12 @@
} }
.indeterminate-bar-secondary { .indeterminate-bar-secondary {
/* stylelint-disable property-blacklist */ /* stylelint-disable property-disallowed-list */
top: 0; top: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
left: -54.888891%; left: -54.888891%;
/* stylelint-enable property-blacklist */ /* stylelint-enable property-disallowed-list */
animation: secondary-indeterminate-translate 2s infinite linear; animation: secondary-indeterminate-translate 2s infinite linear;
@ -125,11 +125,11 @@
.buffer-circles { .buffer-circles {
background-image: radial-gradient(ellipse at center, var(--buffer-background) 0%, var(--buffer-background) 30%, transparent 30%); background-image: radial-gradient(ellipse at center, var(--buffer-background) 0%, var(--buffer-background) 30%, transparent 30%);
/* stylelint-disable property-blacklist */ /* stylelint-disable property-disallowed-list */
background-repeat: repeat-x; background-repeat: repeat-x;
background-position: 5px center; background-position: 5px center;
background-size: 10px 10px; background-size: 10px 10px;
/* stylelint-enable property-blacklist */ /* stylelint-enable property-disallowed-list */
z-index: 0; z-index: 0;
animation: buffering 450ms infinite linear; animation: buffering 450ms infinite linear;

View File

@ -105,12 +105,12 @@
@include margin-horizontal(-13px, null); @include margin-horizontal(-13px, null);
@include multi-dir() { @include multi-dir() {
/* stylelint-disable-next-line property-blacklist */ /* stylelint-disable-next-line property-disallowed-list */
border-radius: 50% 50% 50% 0; border-radius: 50% 50% 50% 0;
} }
@include rtl() { @include rtl() {
/* stylelint-disable-next-line property-blacklist */ /* stylelint-disable-next-line property-disallowed-list */
left: unset; left: unset;
} }

View File

@ -82,7 +82,7 @@
); );
@include rtl() { @include rtl() {
/* stylelint-disable-next-line property-blacklist */ /* stylelint-disable-next-line property-disallowed-list */
left: unset; left: unset;
} }
@ -104,7 +104,7 @@
@include position(calc((var(--height) - var(--bar-height)) / 2), null, null, 0); @include position(calc((var(--height) - var(--bar-height)) / 2), null, null, 0);
@include rtl() { @include rtl() {
/* stylelint-disable-next-line property-blacklist */ /* stylelint-disable-next-line property-disallowed-list */
left: unset; left: unset;
} }
@ -127,7 +127,7 @@
); );
@include rtl() { @include rtl() {
/* stylelint-disable-next-line property-blacklist */ /* stylelint-disable-next-line property-disallowed-list */
left: unset; left: unset;
} }

View File

@ -1,8 +1,8 @@
# ion-router-outlet # ion-router-outlet
Router outlet is a component used in routing within an Angular or Vue app. It behaves in a similar way to Angular's built-in router outlet component and Vue's router view component, but contains the logic for providing a stacked navigation, and animating views in and out. Router outlet is a component used in routing within an Angular, React, or Vue app. It behaves in a similar way to Angular's built-in router outlet component and Vue's router view component, but contains the logic for providing a stacked navigation, and animating views in and out.
> Note: this component should only be used with Angular and Vue projects. For vanilla or Stencil JavaScript projects, use [`ion-router`](../router) and [`ion-route`](../route). > Note: this component should only be used with Angular, React, and Vue projects. For vanilla or Stencil JavaScript projects, use [`ion-router`](../router) and [`ion-route`](../route).
Although router outlet has methods for navigating around, it's recommended to use the navigation methods in your framework's router. Although router outlet has methods for navigating around, it's recommended to use the navigation methods in your framework's router.

View File

@ -19,7 +19,7 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
private waitPromise?: Promise<void>; private waitPromise?: Promise<void>;
private gesture?: Gesture; private gesture?: Gesture;
private ani?: Animation; private ani?: Animation;
private animationEnabled = true; private gestureOrAnimationInProgress = false;
@Element() el!: HTMLElement; @Element() el!: HTMLElement;
@ -61,17 +61,22 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
@Event({ bubbles: false }) ionNavDidChange!: EventEmitter<void>; @Event({ bubbles: false }) ionNavDidChange!: EventEmitter<void>;
async connectedCallback() { async connectedCallback() {
const onStart = () => {
this.gestureOrAnimationInProgress = true;
if (this.swipeHandler) {
this.swipeHandler.onStart();
}
}
this.gesture = (await import('../../utils/gesture/swipe-back')).createSwipeBackGesture( this.gesture = (await import('../../utils/gesture/swipe-back')).createSwipeBackGesture(
this.el, this.el,
() => !!this.swipeHandler && this.swipeHandler.canStart() && this.animationEnabled, () => !this.gestureOrAnimationInProgress && !!this.swipeHandler && this.swipeHandler.canStart(),
() => this.swipeHandler && this.swipeHandler.onStart(), () => onStart(),
step => this.ani && this.ani.progressStep(step), step => this.ani && this.ani.progressStep(step),
(shouldComplete, step, dur) => { (shouldComplete, step, dur) => {
if (this.ani) { if (this.ani) {
this.animationEnabled = false;
this.ani.onFinish(() => { this.ani.onFinish(() => {
this.animationEnabled = true; this.gestureOrAnimationInProgress = false;
if (this.swipeHandler) { if (this.swipeHandler) {
this.swipeHandler.onEnd(shouldComplete); this.swipeHandler.onEnd(shouldComplete);
@ -97,7 +102,8 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
} }
this.ani.progressEnd(shouldComplete ? 1 : 0, newStepValue, dur); this.ani.progressEnd(shouldComplete ? 1 : 0, newStepValue, dur);
} else {
this.gestureOrAnimationInProgress = false;
} }
} }
); );
@ -191,7 +197,34 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
leavingEl, leavingEl,
baseEl: el, baseEl: el,
progressCallback: (opts.progressAnimation progressCallback: (opts.progressAnimation
? ani => this.ani = ani ? ani => {
/**
* Because this progress callback is called asynchronously
* it is possible for the gesture to start and end before
* the animation is ever set. In that scenario, we should
* immediately call progressEnd so that the transition promise
* resolves and the gesture does not get locked up.
*/
if (ani !== undefined && !this.gestureOrAnimationInProgress) {
this.gestureOrAnimationInProgress = true;
ani.onFinish(() => {
this.gestureOrAnimationInProgress = false;
if (this.swipeHandler) {
this.swipeHandler.onEnd(false);
}
}, { oneTimeCallback: true });
/**
* Playing animation to beginning
* with a duration of 0 prevents
* any flickering when the animation
* is later cleaned up.
*/
ani.progressEnd(0, 0, 0);
} else {
this.ani = ani;
}
}
: undefined : undefined
), ),
...opts, ...opts,

View File

@ -61,7 +61,7 @@ span {
animation-timing-function: linear; animation-timing-function: linear;
} }
/* stylelint-disable property-blacklist */ /* stylelint-disable property-disallowed-list */
@keyframes shimmer { @keyframes shimmer {
0% { 0% {
background-position: -400px 0; background-position: -400px 0;
@ -71,4 +71,4 @@ span {
background-position: 400px 0; background-position: 400px 0;
} }
} }
/* stylelint-enable property-blacklist */ /* stylelint-enable property-disallowed-list */

View File

@ -10,6 +10,43 @@ Toasts can be positioned at the top, bottom or middle of the viewport. The posit
The toast can be dismissed automatically after a specific amount of time by passing the number of milliseconds to display it in the `duration` of the toast options. If a button with a role of `"cancel"` is added, then that button will dismiss the toast. To dismiss the toast after creation, call the `dismiss()` method on the instance. The toast can be dismissed automatically after a specific amount of time by passing the number of milliseconds to display it in the `duration` of the toast options. If a button with a role of `"cancel"` is added, then that button will dismiss the toast. To dismiss the toast after creation, call the `dismiss()` method on the instance.
## Interfaces
### ToastButton
```typescript
interface ToastButton {
text?: string;
icon?: string;
side?: 'start' | 'end';
role?: 'cancel' | string;
cssClass?: string | string[];
handler?: () => boolean | void | Promise<boolean | void>;
}
```
### ToastOptions
```typescript
interface ToastOptions {
header?: string;
message?: string | IonicSafeString;
cssClass?: string | string[];
duration?: number;
buttons?: (ToastButton | string)[];
position?: 'top' | 'bottom' | 'middle';
translucent?: boolean;
animated?: boolean;
color?: Color;
mode?: Mode;
keyboardClose?: boolean;
id?: string;
enterAnimation?: AnimationBuilder;
leaveAnimation?: AnimationBuilder;
}
```
<!-- Auto Generated Below --> <!-- Auto Generated Below -->

View File

@ -16,7 +16,7 @@ ion-virtual-scroll > .virtual-loading {
} }
ion-virtual-scroll > .virtual-item { ion-virtual-scroll > .virtual-item {
/* stylelint-disable declaration-no-important, property-blacklist */ /* stylelint-disable declaration-no-important, property-disallowed-list */
position: absolute !important; position: absolute !important;
top: 0 !important; top: 0 !important;

View File

@ -10,6 +10,7 @@ describe('Swipe To Go Back', () => {
cy.ionPageVisible('main'); cy.ionPageVisible('main');
cy.ionNav('ion-item', 'Details'); cy.ionNav('ion-item', 'Details');
cy.ionPageVisible('details'); cy.ionPageVisible('details');
cy.ionPageHidden('main');
cy.ionSwipeToGoBack(true); cy.ionSwipeToGoBack(true);
cy.ionPageVisible('main'); cy.ionPageVisible('main');
}); });

View File

@ -2,7 +2,7 @@
These are React specific building blocks on top of [@ionic/core](https://www.npmjs.com/package/@ionic/core) components/services. These are React specific building blocks on top of [@ionic/core](https://www.npmjs.com/package/@ionic/core) components/services.
To get started, install the Ionic CLI by running `npm i -g ionic`. Then, start a new Ionic React Project by running `ionic start myapp --type=react`. To get started, install the Ionic CLI by running `npm i -g @ionic/cli`. Then, start a new Ionic React Project by running `ionic start myapp --type=react`.
# Current Status of Components # Current Status of Components

View File

@ -26,8 +26,6 @@ export {
AnimationLifecycle, AnimationLifecycle,
createAnimation, createAnimation,
createGesture, createGesture,
AlertButton,
AlertInput,
Gesture, Gesture,
GestureConfig, GestureConfig,
GestureDetail, GestureDetail,
@ -36,7 +34,33 @@ export {
mdTransitionAnimation, mdTransitionAnimation,
NavComponentWithProps, NavComponentWithProps,
setupConfig, setupConfig,
IonicSwiper, IonicSwiper,
SpinnerTypes,
ActionSheetOptions,
ActionSheetButton,
AlertOptions,
AlertInput,
AlertTextareaAttributes,
AlertInputAttributes,
AlertButton,
LoadingOptions,
ModalOptions,
PickerOptions,
PickerButton,
PickerColumn,
PickerColumnOption,
PopoverOptions,
ToastOptions,
ToastButton
} from '@ionic/core'; } from '@ionic/core';
export * from './proxies'; export * from './proxies';

View File

@ -311,6 +311,17 @@ export const IonRouterOutlet = /*@__PURE__*/ defineComponent({
if (!enteringViewItem) { if (!enteringViewItem) {
enteringViewItem = viewStacks.createViewItem(id, matchedRouteRef.value.components.default, matchedRouteRef.value, currentRoute); enteringViewItem = viewStacks.createViewItem(id, matchedRouteRef.value.components.default, matchedRouteRef.value, currentRoute);
viewStacks.add(enteringViewItem); viewStacks.add(enteringViewItem);
/**
* All views that can be transitioned to must have
* an `<ion-page>` element for transitions and lifecycle
* methods to work properly.
*/
if (enteringViewItem.vueComponent?.components?.IonPage === undefined) {
console.warn(`[@ionic/vue Warning]: The view you are trying to render for path ${currentRoute.pathname} does not have the required <ion-page> component. Transitions and lifecycle methods may not work as expected.
See https://ionicframework.com/docs/vue/navigation#ionpage for more information.`);
}
} }
if (!enteringViewItem.mount) { if (!enteringViewItem.mount) {

View File

@ -68,7 +68,32 @@ export {
BackButtonEvent, BackButtonEvent,
// Swiper // Swiper
IonicSwiper IonicSwiper,
SpinnerTypes,
ActionSheetOptions,
ActionSheetButton,
AlertOptions,
AlertInput,
AlertTextareaAttributes,
AlertInputAttributes,
AlertButton,
LoadingOptions,
ModalOptions,
PickerOptions,
PickerButton,
PickerColumn,
PickerColumnOption,
PopoverOptions,
ToastOptions,
ToastButton
} from '@ionic/core/components'; } from '@ionic/core/components';
// Icons that are used by internal components // Icons that are used by internal components