diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index b1c6055fea..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -name: Bug Report -about: Create a report to help us improve -title: 'bug: ' -labels: '' -assignees: '' ---- - - - - - - - - - -# Bug Report - -**Ionic version:** - - -[ ] **4.x** -[ ] **5.x** -[ ] **6.x** - -**Current behavior:** - - -**Expected behavior:** - - -**Steps to reproduce:** - - -**Related code:** - - - -``` -insert short code snippets here -``` - -**Other information:** - - -**Ionic info:** - - -``` -insert the output from ionic info here -``` diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000..d7e064089f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -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. diff --git a/.github/ISSUE_TEMPLATE/cli.md b/.github/ISSUE_TEMPLATE/cli.md deleted file mode 100644 index 213bc40092..0000000000 --- a/.github/ISSUE_TEMPLATE/cli.md +++ /dev/null @@ -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). diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..25a8dbb606 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -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. diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md deleted file mode 100644 index 255df15c4c..0000000000 --- a/.github/ISSUE_TEMPLATE/documentation.md +++ /dev/null @@ -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). diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index b798af2804..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: Feature Request -about: Suggest an idea for this project -title: 'feat: ' -labels: '' -assignees: '' ---- - - - - - - - - - -# Feature Request - -**Ionic version:** - - -[ ] **4.x** -[ ] **5.x** -[ ] **6.x** - -**Describe the Feature Request** - - -**Describe Preferred Solution** - - -**Describe Alternatives** - - -**Related Code** - - -**Additional Context** - diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000000..2a955b4446 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -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. diff --git a/.github/ISSUE_TEMPLATE/support_question.md b/.github/ISSUE_TEMPLATE/support_question.md deleted file mode 100644 index c72195f886..0000000000 --- a/.github/ISSUE_TEMPLATE/support_question.md +++ /dev/null @@ -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/ diff --git a/angular/src/index.ts b/angular/src/index.ts index ba5be94e54..c116ef2076 100644 --- a/angular/src/index.ts +++ b/angular/src/index.ts @@ -46,4 +46,44 @@ export { IonicModule } from './ionic-module'; export { IonicSafeString, getPlatforms, isPlatform, createAnimation, IonicSwiper } from '@ionic/core'; // 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'; diff --git a/core/.stylelintrc.yml b/core/.stylelintrc.yml index 6cd3fda7b9..70a8b0c17b 100644 --- a/core/.stylelintrc.yml +++ b/core/.stylelintrc.yml @@ -254,7 +254,7 @@ rules: - visibility - z-index - property-blacklist: + property-disallowed-list: - background-position - right - left diff --git a/core/src/components/action-sheet/action-sheet.ios.scss b/core/src/components/action-sheet/action-sheet.ios.scss index 126e346f8e..4bd0540db8 100644 --- a/core/src/components/action-sheet/action-sheet.ios.scss +++ b/core/src/components/action-sheet/action-sheet.ios.scss @@ -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; } diff --git a/core/src/components/action-sheet/action-sheet.ios.vars.scss b/core/src/components/action-sheet/action-sheet.ios.vars.scss index 137d19a260..f984d4902e 100644 --- a/core/src/components/action-sheet/action-sheet.ios.vars.scss +++ b/core/src/components/action-sheet/action-sheet.ios.vars.scss @@ -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; diff --git a/core/src/components/action-sheet/action-sheet.tsx b/core/src/components/action-sheet/action-sheet.tsx index 130dd09c92..13879dc310 100644 --- a/core/src/components/action-sheet/action-sheet.tsx +++ b/core/src/components/action-sheet/action-sheet.tsx @@ -258,7 +258,10 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
this.groupEl = el}> {this.header !== undefined && -
+
{this.header} {this.subHeader &&
{this.subHeader}
}
diff --git a/core/src/components/action-sheet/readme.md b/core/src/components/action-sheet/readme.md index 8734dc1d83..691e7b7133 100644 --- a/core/src/components/action-sheet/readme.md +++ b/core/src/components/action-sheet/readme.md @@ -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; +} +``` + +### 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; +} +``` diff --git a/core/src/components/alert/readme.md b/core/src/components/alert/readme.md index ab2785989f..ced2db73c2 100644 --- a/core/src/components/alert/readme.md +++ b/core/src/components/alert/readme.md @@ -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 {} +``` + +### 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 {} +``` + + diff --git a/core/src/components/button/button.scss b/core/src/components/button/button.scss index 44b2288767..00eebd84a8 100644 --- a/core/src/components/button/button.scss +++ b/core/src/components/button/button.scss @@ -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; } diff --git a/core/src/components/content/content.scss b/core/src/components/content/content.scss index a8e7cc4fca..62c7865506 100644 --- a/core/src/components/content/content.scss +++ b/core/src/components/content/content.scss @@ -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%; diff --git a/core/src/components/item-options/item-options.scss b/core/src/components/item-options/item-options.scss index 5e64d23bf9..50774c6856 100644 --- a/core/src/components/item-options/item-options.scss +++ b/core/src/components/item-options/item-options.scss @@ -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() { diff --git a/core/src/components/item-sliding/item-sliding.scss b/core/src/components/item-sliding/item-sliding.scss index 0ff0ae2384..0e2cc06795 100644 --- a/core/src/components/item-sliding/item-sliding.scss +++ b/core/src/components/item-sliding/item-sliding.scss @@ -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%; } diff --git a/core/src/components/item/item.tsx b/core/src/components/item/item.tsx index 0a981ee3a8..cd7f82bc6e 100644 --- a/core/src/components/item/item.tsx +++ b/core/src/components/item/item.tsx @@ -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 ( { + 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); +}); diff --git a/core/src/components/item/test/a11y/index.html b/core/src/components/item/test/a11y/index.html new file mode 100644 index 0000000000..d59901d3cb --- /dev/null +++ b/core/src/components/item/test/a11y/index.html @@ -0,0 +1,105 @@ + + + + + Item - a11y + + + + + + + + + +
+

Item

+ + + Item with Input + + + + + Item disabled with Input + + + + + Item with Input disabled + + + + + Item with Select + + No Game Console + NES + Nintendo64 + PlayStation + Sega Genesis + Sega Saturn + SNES + + + + + Item disabled with Select + + No Game Console + NES + Nintendo64 + PlayStation + Sega Genesis + Sega Saturn + SNES + + + + + Item with Select disabled + + No Game Console + NES + Nintendo64 + PlayStation + Sega Genesis + Sega Saturn + SNES + + + + + Item with Toggle + + + + + Item disabled with Toggle + + + + + Item with Toggle disabled + + + + + Item with Radio + + + + + Item disabled with Radio + + + + + Item with Radio disabled + + + +
+ + diff --git a/core/src/components/loading/readme.md b/core/src/components/loading/readme.md index b9966d8802..6ea4320e70 100644 --- a/core/src/components/loading/readme.md +++ b/core/src/components/loading/readme.md @@ -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; +} +``` @@ -139,17 +161,23 @@ import { IonButton, IonContent, IonPage, useIonLoading } from '@ionic/react'; interface LoadingProps {} const LoadingExample: React.FC = () => { - 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 ( + onClick={() => { present({ - duration: 3000, + message: 'Loading...', + duration: 3000 }) - } + }} > Show Loading diff --git a/core/src/components/loading/usage/react.md b/core/src/components/loading/usage/react.md index b99e0d2951..a9f65cb595 100644 --- a/core/src/components/loading/usage/react.md +++ b/core/src/components/loading/usage/react.md @@ -7,17 +7,23 @@ import { IonButton, IonContent, IonPage, useIonLoading } from '@ionic/react'; interface LoadingProps {} const LoadingExample: React.FC = () => { - 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 ( + onClick={() => { present({ - duration: 3000, + message: 'Loading...', + duration: 3000 }) - } + }} > Show Loading diff --git a/core/src/components/menu-button/menu-button.tsx b/core/src/components/menu-button/menu-button.tsx index fda94193d9..5e2af949f9 100644 --- a/core/src/components/menu-button/menu-button.tsx +++ b/core/src/components/menu-button/menu-button.tsx @@ -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 ( diff --git a/core/src/components/menu-button/test/a11y/e2e.ts b/core/src/components/menu-button/test/a11y/e2e.ts new file mode 100644 index 0000000000..d38ad0fa36 --- /dev/null +++ b/core/src/components/menu-button/test/a11y/e2e.ts @@ -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); +}); diff --git a/core/src/components/menu-button/test/a11y/index.html b/core/src/components/menu-button/test/a11y/index.html new file mode 100644 index 0000000000..f0b0c6d23f --- /dev/null +++ b/core/src/components/menu-button/test/a11y/index.html @@ -0,0 +1,21 @@ + + + + + Menu Button - a11y + + + + + + + + +
+

Menu Button

+ + +
+ + diff --git a/core/src/components/menu-button/test/basic/index.html b/core/src/components/menu-button/test/basic/index.html index 409122163e..00bc406db6 100644 --- a/core/src/components/menu-button/test/basic/index.html +++ b/core/src/components/menu-button/test/basic/index.html @@ -29,6 +29,7 @@ +

Colors

diff --git a/core/src/components/menu/menu.scss b/core/src/components/menu/menu.scss index c02884a5a6..890c424c2e 100644 --- a/core/src/components/menu/menu.scss +++ b/core/src/components/menu/menu.scss @@ -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 */ } } diff --git a/core/src/components/modal/readme.md b/core/src/components/modal/readme.md index 072ce951f8..41d7bb3ef5 100644 --- a/core/src/components/modal/readme.md +++ b/core/src/components/modal/readme.md @@ -102,6 +102,30 @@ ion-modal.stack-modal { } ``` +## Interfaces + +### ModalOptions + +```typescript +interface ModalOptions { + component: T; + componentProps?: ComponentProps; + presentingElement?: HTMLElement; + showBackdrop?: boolean; + backdropDismiss?: boolean; + cssClass?: string | string[]; + animated?: boolean; + swipeToClose?: boolean; + + mode?: Mode; + keyboardClose?: boolean; + id?: string; + + enterAnimation?: AnimationBuilder; + leaveAnimation?: AnimationBuilder; +} +``` + @@ -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)}>

This is more modal content

setShow2ndModal(false)}>Close Modal @@ -811,6 +836,47 @@ export default defineComponent({ > If you need a wrapper element inside of your modal component, we recommend using an `` 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 + + + +``` + ## Properties diff --git a/core/src/components/modal/usage/react.md b/core/src/components/modal/usage/react.md index 1aecc080c8..702e17f681 100644 --- a/core/src/components/modal/usage/react.md +++ b/core/src/components/modal/usage/react.md @@ -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)}>

This is more modal content

setShow2ndModal(false)}>Close Modal diff --git a/core/src/components/modal/usage/vue.md b/core/src/components/modal/usage/vue.md index 47afb4083d..86d06cc394 100644 --- a/core/src/components/modal/usage/vue.md +++ b/core/src/components/modal/usage/vue.md @@ -93,3 +93,44 @@ export default defineComponent({ ``` > If you need a wrapper element inside of your modal component, we recommend using an `` 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 + + + +``` \ No newline at end of file diff --git a/core/src/components/nav/test/nav-controller.spec.ts b/core/src/components/nav/test/nav-controller.spec.ts index e6a6eab9ce..cca03cc5ed 100644 --- a/core/src/components/nav/test/nav-controller.spec.ts +++ b/core/src/components/nav/test/nav-controller.spec.ts @@ -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 = {}; diff --git a/core/src/components/picker/readme.md b/core/src/components/picker/readme.md index bcae3a1a86..e1f001b448 100644 --- a/core/src/components/picker/readme.md +++ b/core/src/components/picker/readme.md @@ -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; +} +``` diff --git a/core/src/components/popover/readme.md b/core/src/components/popover/readme.md index af22da4d6c..1dc57ea4a6 100644 --- a/core/src/components/popover/readme.md +++ b/core/src/components/popover/readme.md @@ -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 diff --git a/core/src/components/progress-bar/progress-bar.scss b/core/src/components/progress-bar/progress-bar.scss index d9bc8dfe0c..0752c79d17 100644 --- a/core/src/components/progress-bar/progress-bar.scss +++ b/core/src/components/progress-bar/progress-bar.scss @@ -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; diff --git a/core/src/components/range/range.md.scss b/core/src/components/range/range.md.scss index 52aa9b23aa..987f286e90 100644 --- a/core/src/components/range/range.md.scss +++ b/core/src/components/range/range.md.scss @@ -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; } diff --git a/core/src/components/range/range.scss b/core/src/components/range/range.scss index 89e6ab6cbb..10c92149b1 100644 --- a/core/src/components/range/range.scss +++ b/core/src/components/range/range.scss @@ -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; } diff --git a/core/src/components/router-outlet/readme.md b/core/src/components/router-outlet/readme.md index 9e7883c0b3..2c6d5917e3 100644 --- a/core/src/components/router-outlet/readme.md +++ b/core/src/components/router-outlet/readme.md @@ -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. diff --git a/core/src/components/router-outlet/route-outlet.tsx b/core/src/components/router-outlet/route-outlet.tsx index bba06a8c63..fe81399802 100644 --- a/core/src/components/router-outlet/route-outlet.tsx +++ b/core/src/components/router-outlet/route-outlet.tsx @@ -19,7 +19,7 @@ export class RouterOutlet implements ComponentInterface, NavOutlet { private waitPromise?: Promise; 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; 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, diff --git a/core/src/components/skeleton-text/skeleton-text.scss b/core/src/components/skeleton-text/skeleton-text.scss index 0f7a69c0f1..455fe41ade 100644 --- a/core/src/components/skeleton-text/skeleton-text.scss +++ b/core/src/components/skeleton-text/skeleton-text.scss @@ -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 */ diff --git a/core/src/components/toast/readme.md b/core/src/components/toast/readme.md index a345835716..ea6324d5b5 100644 --- a/core/src/components/toast/readme.md +++ b/core/src/components/toast/readme.md @@ -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; +} +``` + +### 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; +} +``` diff --git a/core/src/components/virtual-scroll/virtual-scroll.scss b/core/src/components/virtual-scroll/virtual-scroll.scss index df56ac21e3..00ff1c872c 100644 --- a/core/src/components/virtual-scroll/virtual-scroll.scss +++ b/core/src/components/virtual-scroll/virtual-scroll.scss @@ -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; diff --git a/packages/react-router/test-app/cypress/integration/swipe-to-go-back.js b/packages/react-router/test-app/cypress/integration/swipe-to-go-back.js index a7e721ccd2..973ec70da4 100644 --- a/packages/react-router/test-app/cypress/integration/swipe-to-go-back.js +++ b/packages/react-router/test-app/cypress/integration/swipe-to-go-back.js @@ -10,6 +10,7 @@ describe('Swipe To Go Back', () => { cy.ionPageVisible('main'); cy.ionNav('ion-item', 'Details'); cy.ionPageVisible('details'); + cy.ionPageHidden('main'); cy.ionSwipeToGoBack(true); cy.ionPageVisible('main'); }); diff --git a/packages/react/README.md b/packages/react/README.md index fec4540857..9ec88af101 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -2,7 +2,7 @@ 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 diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index 5beed3e662..103889dd27 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -26,8 +26,6 @@ export { AnimationLifecycle, createAnimation, createGesture, - AlertButton, - AlertInput, Gesture, GestureConfig, GestureDetail, @@ -36,7 +34,33 @@ export { mdTransitionAnimation, NavComponentWithProps, setupConfig, + IonicSwiper, + + SpinnerTypes, + + ActionSheetOptions, + ActionSheetButton, + + AlertOptions, + AlertInput, + AlertTextareaAttributes, + AlertInputAttributes, + AlertButton, + + LoadingOptions, + + ModalOptions, + + PickerOptions, + PickerButton, + PickerColumn, + PickerColumnOption, + + PopoverOptions, + + ToastOptions, + ToastButton } from '@ionic/core'; export * from './proxies'; diff --git a/packages/vue/src/components/IonRouterOutlet.ts b/packages/vue/src/components/IonRouterOutlet.ts index 958f252891..14a2ea808f 100644 --- a/packages/vue/src/components/IonRouterOutlet.ts +++ b/packages/vue/src/components/IonRouterOutlet.ts @@ -311,6 +311,17 @@ export const IonRouterOutlet = /*@__PURE__*/ defineComponent({ if (!enteringViewItem) { enteringViewItem = viewStacks.createViewItem(id, matchedRouteRef.value.components.default, matchedRouteRef.value, currentRoute); viewStacks.add(enteringViewItem); + + /** + * All views that can be transitioned to must have + * an `` 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 component. Transitions and lifecycle methods may not work as expected. + +See https://ionicframework.com/docs/vue/navigation#ionpage for more information.`); + } } if (!enteringViewItem.mount) { diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index a7fb9212e4..89d64f0c1c 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -68,7 +68,32 @@ export { BackButtonEvent, // Swiper - IonicSwiper + IonicSwiper, + + SpinnerTypes, + + ActionSheetOptions, + ActionSheetButton, + + AlertOptions, + AlertInput, + AlertTextareaAttributes, + AlertInputAttributes, + AlertButton, + + LoadingOptions, + + ModalOptions, + + PickerOptions, + PickerButton, + PickerColumn, + PickerColumnOption, + + PopoverOptions, + + ToastOptions, + ToastButton } from '@ionic/core/components'; // Icons that are used by internal components