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

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

View File

@ -110,10 +110,16 @@
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 {
@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-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
$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
$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
$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
$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
$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;
/// @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
$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-group" ref={el => this.groupEl = el}>
{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.subHeader && <div class="action-sheet-sub-title">{this.subHeader}</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.
## 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 -->

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

View File

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

View File

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

View File

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

View File

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

View File

@ -299,7 +299,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
render() {
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 clickable = this.isClickable();
const canActivate = this.canActivate();
@ -322,10 +322,11 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
this.itemStyles.forEach(value => {
Object.assign(childStyles, value);
});
const ariaDisabled = (disabled || childStyles['item-interactive-disabled']) ? 'true' : null;
return (
<Host
aria-disabled={disabled ? 'true' : null}
aria-disabled={ariaDisabled}
class={{
...childStyles,
...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.
## 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 -->
@ -139,17 +161,23 @@ import { IonButton, IonContent, IonPage, useIonLoading } from '@ionic/react';
interface 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 (
<IonPage>
<IonContent>
<IonButton
expand="block"
onClick={() =>
onClick={() => {
present({
duration: 3000,
message: 'Loading...',
duration: 3000
})
}
}}
>
Show Loading
</IonButton>

View File

@ -7,17 +7,23 @@ import { IonButton, IonContent, IonPage, useIonLoading } from '@ionic/react';
interface 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 (
<IonPage>
<IonContent>
<IonButton
expand="block"
onClick={() =>
onClick={() => {
present({
duration: 3000,
message: 'Loading...',
duration: 3000
})
}
}}
>
Show Loading
</IonButton>

View File

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

View File

@ -63,7 +63,7 @@
--ion-safe-area-right: 0px;
@include multi-dir() {
/* stylelint-disable property-blacklist */
/* stylelint-disable property-disallowed-list */
right: auto;
left: 0;
}
@ -75,7 +75,7 @@
@include multi-dir() {
right: 0;
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 -->
@ -549,6 +573,7 @@ In most scenarios, setting a ref on `IonRouterOutlet` and passing that ref's `cu
isOpen={show2ndModal}
cssClass='my-custom-class'
presentingElement={firstModalRef.current}
swipeToClose={true}
onDidDismiss={() => setShow2ndModal(false)}>
<p>This is more modal content</p>
<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.
### 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

View File

@ -147,6 +147,7 @@ In most scenarios, setting a ref on `IonRouterOutlet` and passing that ref's `cu
isOpen={show2ndModal}
cssClass='my-custom-class'
presentingElement={firstModalRef.current}
swipeToClose={true}
onDidDismiss={() => setShow2ndModal(false)}>
<p>This is more modal content</p>
<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.
### 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', () => {
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 instance4 = spyOnLifecycles(view4);
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.
## 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 -->

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.
## 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
@ -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.
## 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
### Keyboard Navigation

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
# 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.

View File

@ -19,7 +19,7 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
private waitPromise?: Promise<void>;
private gesture?: Gesture;
private ani?: Animation;
private animationEnabled = true;
private gestureOrAnimationInProgress = false;
@Element() el!: HTMLElement;
@ -61,17 +61,22 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
@Event({ bubbles: false }) ionNavDidChange!: EventEmitter<void>;
async connectedCallback() {
const onStart = () => {
this.gestureOrAnimationInProgress = true;
if (this.swipeHandler) {
this.swipeHandler.onStart();
}
}
this.gesture = (await import('../../utils/gesture/swipe-back')).createSwipeBackGesture(
this.el,
() => !!this.swipeHandler && this.swipeHandler.canStart() && this.animationEnabled,
() => this.swipeHandler && this.swipeHandler.onStart(),
() => !this.gestureOrAnimationInProgress && !!this.swipeHandler && this.swipeHandler.canStart(),
() => onStart(),
step => this.ani && this.ani.progressStep(step),
(shouldComplete, step, dur) => {
if (this.ani) {
this.animationEnabled = false;
this.ani.onFinish(() => {
this.animationEnabled = true;
this.gestureOrAnimationInProgress = false;
if (this.swipeHandler) {
this.swipeHandler.onEnd(shouldComplete);
@ -97,7 +102,8 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
}
this.ani.progressEnd(shouldComplete ? 1 : 0, newStepValue, dur);
} else {
this.gestureOrAnimationInProgress = false;
}
}
);
@ -191,7 +197,34 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
leavingEl,
baseEl: el,
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
),
...opts,

View File

@ -61,7 +61,7 @@ span {
animation-timing-function: linear;
}
/* stylelint-disable property-blacklist */
/* stylelint-disable property-disallowed-list */
@keyframes shimmer {
0% {
background-position: -400px 0;
@ -71,4 +71,4 @@ span {
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.
## 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 -->

View File

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