From 45534255023220ccd615303aa7cff47bd801faa1 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Thu, 27 Jul 2023 13:37:42 -0400 Subject: [PATCH 01/24] chore: add infrastructure for standalone (#27866) --- packages/angular/README.md | 20 ++++++++++++++++++++ packages/angular/common/ng-package.json | 5 +++++ packages/angular/common/src/index.ts | 3 +++ packages/angular/standalone/ng-package.json | 5 +++++ packages/angular/standalone/src/index.ts | 3 +++ 5 files changed, 36 insertions(+) create mode 100644 packages/angular/common/ng-package.json create mode 100644 packages/angular/common/src/index.ts create mode 100644 packages/angular/standalone/ng-package.json create mode 100644 packages/angular/standalone/src/index.ts diff --git a/packages/angular/README.md b/packages/angular/README.md index f44f9d1b564..0a1b2c488d7 100644 --- a/packages/angular/README.md +++ b/packages/angular/README.md @@ -52,3 +52,23 @@ $ npx schematics @ionic/angular:ng-add You'll now be able to add ionic components to a vanilla Angular app setup. + +## Project Structure + +**common** + +This is where logic that is shared between lazy loaded and standalone components live. For example, the lazy loaded IonPopover and standalone IonPopover components extend from a base IonPopover implementation that exists in this directory. + +**Note:** This directory exposes internal APIs and is only accessed in the `standalone` and `src` submodules. Ionic developers should never import directly from `@ionic/angular/common`. Instead, they should import from `@ionic/angular` or `@ionic/angular/standalone`. + +**standalone** + +This is where the standalone component implementations live. It was added as a separate entry point to avoid any lazy loaded logic from accidentally being pulled in to the final build. Having a separate directory allows the lazy loaded implementation to remain accessible from `@ionic/angular` for backwards compatibility. + +Ionic developers can access this by importing from `@ionic/angular/standalone`. + +**src** + +This is where the lazy loaded component implementations live. + +Ionic developers can access this by importing from `@ionic/angular`. diff --git a/packages/angular/common/ng-package.json b/packages/angular/common/ng-package.json new file mode 100644 index 00000000000..b15651d6c2c --- /dev/null +++ b/packages/angular/common/ng-package.json @@ -0,0 +1,5 @@ +{ + "lib": { + "entryFile": "src/index.ts" + }, +} diff --git a/packages/angular/common/src/index.ts b/packages/angular/common/src/index.ts new file mode 100644 index 00000000000..a5a128b2514 --- /dev/null +++ b/packages/angular/common/src/index.ts @@ -0,0 +1,3 @@ +// This is required to get ng-packagr to build. +// Remove this when you actually have something to export +export const placeholder = true; diff --git a/packages/angular/standalone/ng-package.json b/packages/angular/standalone/ng-package.json new file mode 100644 index 00000000000..b15651d6c2c --- /dev/null +++ b/packages/angular/standalone/ng-package.json @@ -0,0 +1,5 @@ +{ + "lib": { + "entryFile": "src/index.ts" + }, +} diff --git a/packages/angular/standalone/src/index.ts b/packages/angular/standalone/src/index.ts new file mode 100644 index 00000000000..a5a128b2514 --- /dev/null +++ b/packages/angular/standalone/src/index.ts @@ -0,0 +1,3 @@ +// This is required to get ng-packagr to build. +// Remove this when you actually have something to export +export const placeholder = true; From af68808e69e42e3e926cfe50f2c5e796a54a1657 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Fri, 28 Jul 2023 10:33:09 -0400 Subject: [PATCH 02/24] test(angular): update test app to account for standalone components (#27867) --- packages/angular/test/README.md | 8 ++ .../apps/ng14/e2e/src/angular-version.spec.ts | 2 +- .../apps/ng15/e2e/src/angular-version.spec.ts | 2 +- .../apps/ng16/e2e/src/angular-version.spec.ts | 2 +- .../e2e/src/bind-component-inputs.spec.ts | 2 +- .../ng16/e2e/src/modal-nav-params.spec.ts | 2 +- .../test/base/e2e/src/accordion.spec.ts | 2 +- .../base/e2e/src/form-controls/range.spec.ts | 2 +- .../angular/test/base/e2e/src/form.spec.ts | 2 +- .../test/base/e2e/src/inline-overlays.spec.ts | 2 +- .../angular/test/base/e2e/src/inputs.spec.ts | 2 +- .../e2e/src/keep-contents-mounted.spec.ts | 14 +-- .../angular/test/base/e2e/src/modal.spec.ts | 6 +- .../test/base/e2e/src/navigation.spec.ts | 4 +- .../test/base/e2e/src/nested-outlet.spec.ts | 2 +- .../angular/test/base/e2e/src/popover.spec.ts | 2 +- .../test/base/e2e/src/providers.spec.ts | 6 +- .../test/base/e2e/src/router-link.spec.ts | 8 +- .../angular/test/base/e2e/src/routing.spec.ts | 2 +- .../test/base/e2e/src/searchbar.spec.ts | 2 +- .../base/e2e/src/standalone-routing.spec.ts | 2 +- .../angular/test/base/e2e/src/tabs.spec.ts | 16 ++-- .../test/base/e2e/src/textarea.spec.ts | 2 +- .../test/base/e2e/src/view-child.spec.ts | 2 +- .../src/app/app-lazy/app-routing.module.ts | 10 +++ .../base/src/app/app-lazy/app.component.html | 3 + .../base/src/app/app-lazy/app.component.ts | 8 ++ .../test/base/src/app/app-lazy/app.module.ts | 64 +++++++++++++ .../test/base/src/app/app-lazy/app.routes.ts | 89 +++++++++++++++++++ .../src/app/app-standalone/app.component.html | 1 + .../src/app/app-standalone/app.component.ts | 21 +++++ .../base/src/app/app-standalone/app.routes.ts | 13 +++ .../test/base/src/app/app.component.css | 0 .../test/base/src/app/app.component.html | 4 +- .../test/base/src/app/app.component.ts | 3 +- .../angular/test/base/src/app/app.module.ts | 63 +++---------- .../angular/test/base/src/app/app.routes.ts | 86 +++--------------- .../app/home-page/home-page.component.html | 20 ++--- .../navigation-page1.component.ts | 2 +- .../navigation-page2.component.ts | 2 +- .../navigation-page3.component.ts | 2 +- .../nested-outlet-page.component.html | 4 +- .../nested-outlet-page2.component.html | 2 +- .../router-link-page2.component.html | 4 +- .../router-link/router-link.component.html | 14 +-- .../app/router-link/router-link.component.ts | 8 +- .../app/standalone/standalone.component.html | 2 +- .../tabs-tab3-nested.component.html | 6 +- .../tabs-tab3/tabs-tab3.component.html | 6 +- .../tabs-tab1-nested.component.html | 10 +-- .../app/tabs-tab1/tabs-tab1.component.html | 8 +- .../app/tabs-tab2/tabs-tab2.component.html | 6 +- .../base/src/app/test/test.component.html | 1 + .../test/base/src/app/test/test.component.ts | 12 +++ 54 files changed, 346 insertions(+), 224 deletions(-) create mode 100644 packages/angular/test/base/src/app/app-lazy/app-routing.module.ts create mode 100644 packages/angular/test/base/src/app/app-lazy/app.component.html create mode 100644 packages/angular/test/base/src/app/app-lazy/app.component.ts create mode 100644 packages/angular/test/base/src/app/app-lazy/app.module.ts create mode 100644 packages/angular/test/base/src/app/app-lazy/app.routes.ts create mode 100644 packages/angular/test/base/src/app/app-standalone/app.component.html create mode 100644 packages/angular/test/base/src/app/app-standalone/app.component.ts create mode 100644 packages/angular/test/base/src/app/app-standalone/app.routes.ts delete mode 100644 packages/angular/test/base/src/app/app.component.css create mode 100644 packages/angular/test/base/src/app/test/test.component.html create mode 100644 packages/angular/test/base/src/app/test/test.component.ts diff --git a/packages/angular/test/README.md b/packages/angular/test/README.md index 44c1a5e1daf..01756c7793d 100644 --- a/packages/angular/test/README.md +++ b/packages/angular/test/README.md @@ -65,6 +65,14 @@ If you want to add a version-specific change, add the change inside of the appro If you need to add E2E tests that are only run on a specific version of the JS Framework, replicate the `VersionTest` component on each partial application. This ensures that tests for framework version X do not get run for framework version Y. +### Testing Lazy Loaded Ionic Components + +Tests for lazy loaded Ionic UI components should only be added under the `/lazy` route. This ensures the `IonicModule` is added. + +### Testing Standalone Ionic Components + +Tests for standalone Ionic UI components should only be added under the `/standalone` route. This allows for an isolated environment where the lazy loaded `IonicModule` is not initialized. The standalone components use Stencil's custom element bundle instead of the lazy loaded bundle. If `IonicModule` is initialized then the Stencil components will fall back to using the lazy loaded implementation instead of the custom elements bundle implementation. + ## Adding New Test Apps As we add support for new versions of Angular, we will also need to update this directory to test against new applications. The following steps can serve as a guide for adding new apps: diff --git a/packages/angular/test/apps/ng14/e2e/src/angular-version.spec.ts b/packages/angular/test/apps/ng14/e2e/src/angular-version.spec.ts index 9b4fa16f030..d73c7daa90b 100644 --- a/packages/angular/test/apps/ng14/e2e/src/angular-version.spec.ts +++ b/packages/angular/test/apps/ng14/e2e/src/angular-version.spec.ts @@ -1,5 +1,5 @@ it("should be on Angular 14", () => { - cy.visit('/'); + cy.visit('/lazy'); cy.get('ion-title').contains('Angular 14'); }); diff --git a/packages/angular/test/apps/ng15/e2e/src/angular-version.spec.ts b/packages/angular/test/apps/ng15/e2e/src/angular-version.spec.ts index b25fc2ab491..9cf6670ef7f 100644 --- a/packages/angular/test/apps/ng15/e2e/src/angular-version.spec.ts +++ b/packages/angular/test/apps/ng15/e2e/src/angular-version.spec.ts @@ -1,5 +1,5 @@ it("should be on Angular 15", () => { - cy.visit('/'); + cy.visit('/lazy'); cy.get('ion-title').contains('Angular 15'); }); diff --git a/packages/angular/test/apps/ng16/e2e/src/angular-version.spec.ts b/packages/angular/test/apps/ng16/e2e/src/angular-version.spec.ts index 53ff4d89da1..2c5e5f3d0fd 100644 --- a/packages/angular/test/apps/ng16/e2e/src/angular-version.spec.ts +++ b/packages/angular/test/apps/ng16/e2e/src/angular-version.spec.ts @@ -1,5 +1,5 @@ it("should be on Angular 16", () => { - cy.visit('/'); + cy.visit('/lazy'); cy.get('ion-title').contains('Angular 16'); }); diff --git a/packages/angular/test/apps/ng16/e2e/src/bind-component-inputs.spec.ts b/packages/angular/test/apps/ng16/e2e/src/bind-component-inputs.spec.ts index 9f60cb0f8ee..651747730b3 100644 --- a/packages/angular/test/apps/ng16/e2e/src/bind-component-inputs.spec.ts +++ b/packages/angular/test/apps/ng16/e2e/src/bind-component-inputs.spec.ts @@ -1,5 +1,5 @@ it("binding route data to inputs should work", () => { - cy.visit('/version-test/bind-route/test?query=test'); + cy.visit('/lazy/version-test/bind-route/test?query=test'); cy.get('#route-params').contains('test'); cy.get('#query-params').contains('test'); diff --git a/packages/angular/test/apps/ng16/e2e/src/modal-nav-params.spec.ts b/packages/angular/test/apps/ng16/e2e/src/modal-nav-params.spec.ts index 6a5556518f4..80c24f1af3b 100644 --- a/packages/angular/test/apps/ng16/e2e/src/modal-nav-params.spec.ts +++ b/packages/angular/test/apps/ng16/e2e/src/modal-nav-params.spec.ts @@ -1,7 +1,7 @@ describe('Modal Nav Params', () => { beforeEach(() => { - cy.visit('/version-test/modal-nav-params'); + cy.visit('/lazy/version-test/modal-nav-params'); }); it('should assign the rootParams when presented in a modal multiple times', () => { diff --git a/packages/angular/test/base/e2e/src/accordion.spec.ts b/packages/angular/test/base/e2e/src/accordion.spec.ts index 93455838cf6..7d57fe19d55 100644 --- a/packages/angular/test/base/e2e/src/accordion.spec.ts +++ b/packages/angular/test/base/e2e/src/accordion.spec.ts @@ -1,6 +1,6 @@ describe('Accordion', () => { beforeEach(() => { - cy.visit('/accordions'); + cy.visit('/lazy/accordions'); }); it('should correctly expand on multiple modal opens', () => { diff --git a/packages/angular/test/base/e2e/src/form-controls/range.spec.ts b/packages/angular/test/base/e2e/src/form-controls/range.spec.ts index 386ba7ac608..e2e4120b01b 100644 --- a/packages/angular/test/base/e2e/src/form-controls/range.spec.ts +++ b/packages/angular/test/base/e2e/src/form-controls/range.spec.ts @@ -1,7 +1,7 @@ describe('Form Controls: Range', () => { beforeEach(() => { - cy.visit('/form-controls/range'); + cy.visit('/lazy/form-controls/range'); }); it('should have form control initial value', () => { diff --git a/packages/angular/test/base/e2e/src/form.spec.ts b/packages/angular/test/base/e2e/src/form.spec.ts index 39995f2f5a2..edfce512ce2 100644 --- a/packages/angular/test/base/e2e/src/form.spec.ts +++ b/packages/angular/test/base/e2e/src/form.spec.ts @@ -1,6 +1,6 @@ describe('Form', () => { beforeEach(() => { - cy.visit('/form'); + cy.visit('/lazy/form'); }) describe('status updates', () => { diff --git a/packages/angular/test/base/e2e/src/inline-overlays.spec.ts b/packages/angular/test/base/e2e/src/inline-overlays.spec.ts index febc6d82a26..234cbb892a0 100644 --- a/packages/angular/test/base/e2e/src/inline-overlays.spec.ts +++ b/packages/angular/test/base/e2e/src/inline-overlays.spec.ts @@ -1,6 +1,6 @@ describe('Overlays: Inline', () => { beforeEach(() => { - cy.visit('/overlays-inline'); + cy.visit('/lazy/overlays-inline'); }); describe('Alert', () => { diff --git a/packages/angular/test/base/e2e/src/inputs.spec.ts b/packages/angular/test/base/e2e/src/inputs.spec.ts index 2b076f6284f..d79f3b92a6d 100644 --- a/packages/angular/test/base/e2e/src/inputs.spec.ts +++ b/packages/angular/test/base/e2e/src/inputs.spec.ts @@ -1,6 +1,6 @@ describe('Inputs', () => { beforeEach(() => { - cy.visit('/inputs'); + cy.visit('/lazy/inputs'); }) it('should have default value', () => { diff --git a/packages/angular/test/base/e2e/src/keep-contents-mounted.spec.ts b/packages/angular/test/base/e2e/src/keep-contents-mounted.spec.ts index 44e47f82530..8d873d0ae27 100644 --- a/packages/angular/test/base/e2e/src/keep-contents-mounted.spec.ts +++ b/packages/angular/test/base/e2e/src/keep-contents-mounted.spec.ts @@ -1,19 +1,19 @@ describe('overlays - keepContentsMounted', () => { describe('modal', () => { it('should not mount component if false', () => { - cy.visit('/modal-inline'); + cy.visit('/lazy/modal-inline'); cy.get('ion-modal ion-content').should('not.exist'); }); it('should mount component if true', () => { - cy.visit('/keep-contents-mounted'); + cy.visit('/lazy/keep-contents-mounted'); cy.get('ion-modal ion-content').should('exist'); }); it('should keep component mounted after dismissing if true', () => { - cy.visit('/keep-contents-mounted'); + cy.visit('/lazy/keep-contents-mounted'); cy.get('#open-modal').click(); @@ -29,26 +29,26 @@ describe('overlays - keepContentsMounted', () => { }); it('should has ion-delegate-host on mount', () => { - cy.visit('/keep-contents-mounted'); + cy.visit('/lazy/keep-contents-mounted'); cy.get('ion-modal .ion-delegate-host').should('exist'); }); }) describe('popover', () => { it('should not mount component if false', () => { - cy.visit('/popover-inline'); + cy.visit('/lazy/popover-inline'); cy.get('ion-popover ion-content').should('not.exist'); }); it('should mount component if true', () => { - cy.visit('/keep-contents-mounted'); + cy.visit('/lazy/keep-contents-mounted'); cy.get('ion-popover ion-content').should('exist'); }); it('should keep component mounted after dismissing if true', () => { - cy.visit('/keep-contents-mounted'); + cy.visit('/lazy/keep-contents-mounted'); cy.get('#open-popover').click(); diff --git a/packages/angular/test/base/e2e/src/modal.spec.ts b/packages/angular/test/base/e2e/src/modal.spec.ts index 153da005ad3..4b41039eac3 100644 --- a/packages/angular/test/base/e2e/src/modal.spec.ts +++ b/packages/angular/test/base/e2e/src/modal.spec.ts @@ -1,6 +1,6 @@ describe('Modals', () => { beforeEach(() => { - cy.visit('/modals'); + cy.visit('/lazy/modals'); }) it('should open standalone modal and close', () => { @@ -45,7 +45,7 @@ describe('Modals', () => { describe('Modals: Inline', () => { beforeEach(() => { - cy.visit('/modal-inline'); + cy.visit('/lazy/modal-inline'); }); it('should initially have no items', () => { @@ -95,7 +95,7 @@ describe('Modals: Inline', () => { describe('when in a modal', () => { beforeEach(() => { - cy.visit('/modals'); + cy.visit('/lazy/modals'); cy.get('#action-button').click(); cy.get('#close-modal').click(); cy.get('#action-button').click(); diff --git a/packages/angular/test/base/e2e/src/navigation.spec.ts b/packages/angular/test/base/e2e/src/navigation.spec.ts index 404ab86c30e..17c1f93a288 100644 --- a/packages/angular/test/base/e2e/src/navigation.spec.ts +++ b/packages/angular/test/base/e2e/src/navigation.spec.ts @@ -1,10 +1,10 @@ describe('Navigation', () => { beforeEach(() => { - cy.visit('/navigation'); + cy.visit('/lazy/navigation'); }) it('should navigate correctly', () => { - cy.visit('/navigation/page1'); + cy.visit('/lazy/navigation/page1'); cy.wait(2000); cy.testStack('ion-router-outlet', ['app-navigation-page2', 'app-navigation-page1']); diff --git a/packages/angular/test/base/e2e/src/nested-outlet.spec.ts b/packages/angular/test/base/e2e/src/nested-outlet.spec.ts index ef5405905d6..8a95ab408c9 100644 --- a/packages/angular/test/base/e2e/src/nested-outlet.spec.ts +++ b/packages/angular/test/base/e2e/src/nested-outlet.spec.ts @@ -1,6 +1,6 @@ describe('Nested Outlet', () => { beforeEach(() => { - cy.visit('/nested-outlet/page'); + cy.visit('/lazy/nested-outlet/page'); }) it('should navigate correctly', () => { diff --git a/packages/angular/test/base/e2e/src/popover.spec.ts b/packages/angular/test/base/e2e/src/popover.spec.ts index 17f8fe64455..20500bb5c32 100644 --- a/packages/angular/test/base/e2e/src/popover.spec.ts +++ b/packages/angular/test/base/e2e/src/popover.spec.ts @@ -1,6 +1,6 @@ describe('Popovers: Inline', () => { beforeEach(() => { - cy.visit('/popover-inline'); + cy.visit('/lazy/popover-inline'); }); it('should initially have no items', () => { diff --git a/packages/angular/test/base/e2e/src/providers.spec.ts b/packages/angular/test/base/e2e/src/providers.spec.ts index e373454e404..215aa13cb4f 100644 --- a/packages/angular/test/base/e2e/src/providers.spec.ts +++ b/packages/angular/test/base/e2e/src/providers.spec.ts @@ -1,6 +1,6 @@ describe('Providers', () => { beforeEach(() => { - cy.visit('/providers'); + cy.visit('/lazy/providers'); }) it('should load all providers', () => { @@ -17,13 +17,13 @@ describe('Providers', () => { }); it('should detect testing mode', () => { - cy.visit('/providers?ionic:_testing=true'); + cy.visit('/lazy/providers?ionic:_testing=true'); cy.get('#is-testing').should('have.text', 'true'); }); it('should get query params', () => { - cy.visit('/providers?firstParam=abc&secondParam=true'); + cy.visit('/lazy/providers?firstParam=abc&secondParam=true'); cy.get('#query-params').should('have.text', 'firstParam: abc, firstParam: true'); }) diff --git a/packages/angular/test/base/e2e/src/router-link.spec.ts b/packages/angular/test/base/e2e/src/router-link.spec.ts index ba169906adf..5ca72825f51 100644 --- a/packages/angular/test/base/e2e/src/router-link.spec.ts +++ b/packages/angular/test/base/e2e/src/router-link.spec.ts @@ -1,6 +1,6 @@ describe('Router Link', () => { beforeEach(() => { - cy.visit('/router-link'); + cy.visit('/lazy/router-link'); }); describe('router-link params and fragments', () => { @@ -9,7 +9,7 @@ describe('Router Link', () => { const id = 'MyPageID=='; it('should go to a page with properly encoded values', () => { - cy.visit('/router-link?ionic:_testing=true'); + cy.visit('/lazy/router-link?ionic:_testing=true'); cy.get('#queryParamsFragment').click(); const expectedPath = `${encodeURIComponent(id)}`; @@ -24,7 +24,7 @@ describe('Router Link', () => { }); it('should return to a page with preserved query param and fragment', () => { - cy.visit('/router-link?ionic:_testing=true'); + cy.visit('/lazy/router-link?ionic:_testing=true'); cy.get('#queryParamsFragment').click(); cy.get('#goToPage3').click(); @@ -46,7 +46,7 @@ describe('Router Link', () => { }); it('should preserve query param and fragment with defaultHref string', () => { - cy.visit('/router-link-page3?ionic:_testing=true'); + cy.visit('/lazy/router-link-page3?ionic:_testing=true'); cy.get('#goBackFromPage3').click(); diff --git a/packages/angular/test/base/e2e/src/routing.spec.ts b/packages/angular/test/base/e2e/src/routing.spec.ts index 9521ff27494..b91894fea57 100644 --- a/packages/angular/test/base/e2e/src/routing.spec.ts +++ b/packages/angular/test/base/e2e/src/routing.spec.ts @@ -1,6 +1,6 @@ describe('Routing', () => { beforeEach(() => { - cy.visit('/router-link?ionic:mode=ios'); + cy.visit('/lazy/router-link?ionic:mode=ios'); }) it('should swipe and abort', () => { diff --git a/packages/angular/test/base/e2e/src/searchbar.spec.ts b/packages/angular/test/base/e2e/src/searchbar.spec.ts index e35f4088dd2..7bd40042fe3 100644 --- a/packages/angular/test/base/e2e/src/searchbar.spec.ts +++ b/packages/angular/test/base/e2e/src/searchbar.spec.ts @@ -1,5 +1,5 @@ describe('Searchbar', () => { - beforeEach(() => cy.visit('/searchbar')); + beforeEach(() => cy.visit('/lazy/searchbar')); it('should become valid', () => { cy.get('#status').should('have.text', 'INVALID'); diff --git a/packages/angular/test/base/e2e/src/standalone-routing.spec.ts b/packages/angular/test/base/e2e/src/standalone-routing.spec.ts index 890f8c4d982..4b5bbdfb94a 100644 --- a/packages/angular/test/base/e2e/src/standalone-routing.spec.ts +++ b/packages/angular/test/base/e2e/src/standalone-routing.spec.ts @@ -1,6 +1,6 @@ describe('Routing with Standalone Components', () => { beforeEach(() => { - cy.visit('/standalone'); + cy.visit('/lazy/standalone'); }); it('should render the component', () => { diff --git a/packages/angular/test/base/e2e/src/tabs.spec.ts b/packages/angular/test/base/e2e/src/tabs.spec.ts index 48d1dd5c6db..e7c4b51cfbf 100644 --- a/packages/angular/test/base/e2e/src/tabs.spec.ts +++ b/packages/angular/test/base/e2e/src/tabs.spec.ts @@ -1,6 +1,6 @@ describe('Tabs', () => { beforeEach(() => { - cy.visit('/tabs'); + cy.visit('/lazy/tabs'); }) describe('entry url - /tabs', () => { @@ -214,7 +214,7 @@ describe('Tabs', () => { describe('entry tab contains navigation extras', () => { const expectNestedTabUrlToContain = 'search=hello#fragment'; const rootUrlParams = 'test=123#rootFragment'; - const rootUrl = `/tabs/account?${rootUrlParams}`; + const rootUrl = `/lazy/tabs/account?${rootUrlParams}`; beforeEach(() => { cy.visit(rootUrl); @@ -288,7 +288,7 @@ describe('Tabs', () => { describe('entry url - /tabs/account', () => { beforeEach(() => { - cy.visit('/tabs/account'); + cy.visit('/lazy/tabs/account'); }); it('should pop to previous view when leaving tabs outlet', () => { @@ -322,7 +322,7 @@ describe('Tabs', () => { describe('entry url - /', () => { it('should pop to the root outlet from the tabs outlet', () => { - cy.visit('/'); + cy.visit('/lazy/'); cy.get('ion-title').should('contain.text', 'Test App'); @@ -356,7 +356,7 @@ describe('Tabs', () => { describe('entry url - /tabs/account/nested/1', () => { beforeEach(() => { - cy.visit('/tabs/account/nested/1'); + cy.visit('/lazy/tabs/account/nested/1'); }) it('should only display the back-button when there is a page in the stack', () => { @@ -401,7 +401,7 @@ describe('Tabs', () => { describe('entry url - /tabs/lazy', () => { beforeEach(() => { - cy.visit('/tabs/lazy'); + cy.visit('/lazy/tabs/lazy'); }); it('should not display the back-button if coming from a different stack', () => { @@ -419,7 +419,7 @@ describe('Tabs', () => { describe('enter url - /tabs/contact/one', () => { beforeEach(() => { - cy.visit('/tabs/contact/one'); + cy.visit('/lazy/tabs/contact/one'); }); it('should return to correct tab after going to page in different outlet', () => { @@ -436,7 +436,7 @@ describe('Tabs', () => { }) it('Tabs should support conditional slots', () => { - cy.visit('/tabs-slots'); + cy.visit('/lazy/tabs-slots'); cy.get('ion-tabs .tabs-inner + ion-tab-bar').should('have.length', 1); diff --git a/packages/angular/test/base/e2e/src/textarea.spec.ts b/packages/angular/test/base/e2e/src/textarea.spec.ts index 3db090e2e65..33f2c557a0e 100644 --- a/packages/angular/test/base/e2e/src/textarea.spec.ts +++ b/packages/angular/test/base/e2e/src/textarea.spec.ts @@ -1,5 +1,5 @@ describe('Textarea', () => { - beforeEach(() => cy.visit('/textarea')); + beforeEach(() => cy.visit('/lazy/textarea')); it('should become valid', () => { cy.get('#status').should('have.text', 'INVALID'); diff --git a/packages/angular/test/base/e2e/src/view-child.spec.ts b/packages/angular/test/base/e2e/src/view-child.spec.ts index 6c3736c97e9..098acc0ea08 100644 --- a/packages/angular/test/base/e2e/src/view-child.spec.ts +++ b/packages/angular/test/base/e2e/src/view-child.spec.ts @@ -1,6 +1,6 @@ describe('View Child', () => { beforeEach(() => { - cy.visit('/view-child'); + cy.visit('/lazy/view-child'); }) it('should get a reference to all children', () => { diff --git a/packages/angular/test/base/src/app/app-lazy/app-routing.module.ts b/packages/angular/test/base/src/app/app-lazy/app-routing.module.ts new file mode 100644 index 00000000000..e5aade4dcc0 --- /dev/null +++ b/packages/angular/test/base/src/app/app-lazy/app-routing.module.ts @@ -0,0 +1,10 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { routes } from './app.routes'; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule { } diff --git a/packages/angular/test/base/src/app/app-lazy/app.component.html b/packages/angular/test/base/src/app/app-lazy/app.component.html new file mode 100644 index 00000000000..13b96776ee1 --- /dev/null +++ b/packages/angular/test/base/src/app/app-lazy/app.component.html @@ -0,0 +1,3 @@ + + + diff --git a/packages/angular/test/base/src/app/app-lazy/app.component.ts b/packages/angular/test/base/src/app/app-lazy/app.component.ts new file mode 100644 index 00000000000..ead0de4491a --- /dev/null +++ b/packages/angular/test/base/src/app/app-lazy/app.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html' +}) +export class AppComponent { +} diff --git a/packages/angular/test/base/src/app/app-lazy/app.module.ts b/packages/angular/test/base/src/app/app-lazy/app.module.ts new file mode 100644 index 00000000000..c7c911da16e --- /dev/null +++ b/packages/angular/test/base/src/app/app-lazy/app.module.ts @@ -0,0 +1,64 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; + +import { AppRoutingModule } from './app-routing.module'; +import { AppComponent } from './app.component'; +import { IonicModule } from '@ionic/angular'; +import { FormsModule } from '@angular/forms'; +import { InputsComponent } from '../inputs/inputs.component'; +import { ModalComponent } from '../modal/modal.component'; +import { ModalExampleComponent } from '../modal-example/modal-example.component'; +import { RouterLinkComponent } from '../router-link/router-link.component'; +import { RouterLinkPageComponent } from '../router-link-page/router-link-page.component'; +import { RouterLinkPage2Component } from '../router-link-page2/router-link-page2.component'; +import { RouterLinkPage3Component } from '../router-link-page3/router-link-page3.component'; +import { HomePageComponent } from '../home-page/home-page.component'; +import { NestedOutletComponent } from '../nested-outlet/nested-outlet.component'; +import { NestedOutletPageComponent } from '../nested-outlet-page/nested-outlet-page.component'; +import { NestedOutletPage2Component } from '../nested-outlet-page2/nested-outlet-page2.component'; +import { NavComponent } from '../nav/nav.component'; +import { ViewChildComponent } from '../view-child/view-child.component'; +import { ProvidersComponent } from '../providers/providers.component'; +import { FormComponent } from '../form/form.component'; +import { NavigationPage1Component } from '../navigation-page1/navigation-page1.component'; +import { NavigationPage2Component } from '../navigation-page2/navigation-page2.component'; +import { NavigationPage3Component } from '../navigation-page3/navigation-page3.component'; +import { AlertComponent } from '../alert/alert.component'; +import { AccordionComponent } from '../accordion/accordion.component'; +import { AccordionModalComponent } from '../accordion/accordion-modal/accordion-modal.component'; + +@NgModule({ + declarations: [ + AppComponent, + InputsComponent, + ModalComponent, + ModalExampleComponent, + RouterLinkComponent, + RouterLinkPageComponent, + RouterLinkPage2Component, + RouterLinkPage3Component, + HomePageComponent, + NestedOutletComponent, + NestedOutletPageComponent, + NestedOutletPage2Component, + NavComponent, + ViewChildComponent, + ProvidersComponent, + FormComponent, + NavigationPage1Component, + NavigationPage2Component, + NavigationPage3Component, + AlertComponent, + AccordionComponent, + AccordionModalComponent + ], + imports: [ + CommonModule, + AppRoutingModule, + FormsModule, + ReactiveFormsModule, + IonicModule.forRoot({ keyboardHeight: 12345 }), + ] +}) +export class AppModule { } diff --git a/packages/angular/test/base/src/app/app-lazy/app.routes.ts b/packages/angular/test/base/src/app/app-lazy/app.routes.ts new file mode 100644 index 00000000000..7d2c12b9bed --- /dev/null +++ b/packages/angular/test/base/src/app/app-lazy/app.routes.ts @@ -0,0 +1,89 @@ +import { Routes } from '@angular/router'; +import { AppComponent } from './app.component'; +import { InputsComponent } from '../inputs/inputs.component'; +import { ModalComponent } from '../modal/modal.component'; +import { RouterLinkComponent } from '../router-link/router-link.component'; +import { RouterLinkPageComponent } from '../router-link-page/router-link-page.component'; +import { RouterLinkPage2Component } from '../router-link-page2/router-link-page2.component'; +import { RouterLinkPage3Component } from '../router-link-page3/router-link-page3.component'; +import { HomePageComponent } from '../home-page/home-page.component'; +import { NestedOutletComponent } from '../nested-outlet/nested-outlet.component'; +import { NestedOutletPageComponent } from '../nested-outlet-page/nested-outlet-page.component'; +import { NestedOutletPage2Component } from '../nested-outlet-page2/nested-outlet-page2.component'; +import { ViewChildComponent } from '../view-child/view-child.component'; +import { ProvidersComponent } from '../providers/providers.component'; +import { FormComponent } from '../form/form.component'; +import { NavigationPage1Component } from '../navigation-page1/navigation-page1.component'; +import { NavigationPage2Component } from '../navigation-page2/navigation-page2.component'; +import { NavigationPage3Component } from '../navigation-page3/navigation-page3.component'; +import { AlertComponent } from '../alert/alert.component'; +import { AccordionComponent } from '../accordion/accordion.component'; + +export const routes: Routes = [ + { + path: '', + component: AppComponent, + children: [ + { path: '', component: HomePageComponent }, + { path: 'version-test', loadChildren: () => import('../version-test').then(m => m.VersionTestModule) }, + { path: 'accordions', component: AccordionComponent }, + { path: 'alerts', component: AlertComponent }, + { path: 'inputs', component: InputsComponent }, + { path: 'textarea', loadChildren: () => import('../textarea/textarea.module').then(m => m.TextareaModule) }, + { path: 'searchbar', loadChildren: () => import('../searchbar/searchbar.module').then(m => m.SearchbarModule) }, + { path: 'form', component: FormComponent }, + { path: 'modals', component: ModalComponent }, + { path: 'modal-inline', loadChildren: () => import('../modal-inline').then(m => m.ModalInlineModule) }, + { path: 'view-child', component: ViewChildComponent }, + { path: 'keep-contents-mounted', loadChildren: () => import('../keep-contents-mounted').then(m => m.OverlayAutoMountModule) }, + { path: 'overlays-inline', loadChildren: () => import('../overlays-inline').then(m => m.OverlaysInlineModule) }, + { path: 'popover-inline', loadChildren: () => import('../popover-inline').then(m => m.PopoverInlineModule) }, + { path: 'providers', component: ProvidersComponent }, + { path: 'router-link', component: RouterLinkComponent }, + { path: 'router-link-page', component: RouterLinkPageComponent }, + { path: 'router-link-page2/:id', component: RouterLinkPage2Component }, + { path: 'router-link-page3', component: RouterLinkPage3Component }, + { path: 'standalone', loadComponent: () => import('../standalone/standalone.component').then(c => c.StandaloneComponent) }, + { path: 'tabs', redirectTo: '/lazy/tabs/account', pathMatch: 'full' }, + { + path: 'navigation', + children: [ + { path: 'page1', component: NavigationPage1Component }, + { path: 'page2', component: NavigationPage2Component }, + { path: 'page3', component: NavigationPage3Component } + ] + }, + { + path: 'tabs', + loadChildren: () => import('../tabs/tabs.module').then(m => m.TabsPageModule) + }, + { + path: 'tabs-global', + loadChildren: () => import('../tabs-global/tabs-global.module').then(m => m.TabsGlobalModule) + }, + { + path: 'tabs-slots', + loadComponent: () => import('../tabs-slots.component').then(c => c.TabsSlotsComponent) + }, + { + path: 'nested-outlet', + component: NestedOutletComponent, + children: [ + { + path: 'page', + component: NestedOutletPageComponent + }, + { + path: 'page2', + component: NestedOutletPage2Component + } + ] + }, + { + path: 'form-controls/range', + loadChildren: () => import('../form-controls/range/range.module').then(m => m.RangeModule) + } + ] + }, +]; + diff --git a/packages/angular/test/base/src/app/app-standalone/app.component.html b/packages/angular/test/base/src/app/app-standalone/app.component.html new file mode 100644 index 00000000000..0680b43f9c6 --- /dev/null +++ b/packages/angular/test/base/src/app/app-standalone/app.component.html @@ -0,0 +1 @@ + diff --git a/packages/angular/test/base/src/app/app-standalone/app.component.ts b/packages/angular/test/base/src/app/app-standalone/app.component.ts new file mode 100644 index 00000000000..b8ba6d5caa7 --- /dev/null +++ b/packages/angular/test/base/src/app/app-standalone/app.component.ts @@ -0,0 +1,21 @@ +import { Component } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +/** + * This temporary code initialized Ionic and ensures components are visible. + * TODO FW-4766 Can be removed when ticket is implemented + */ +import { initialize } from '@ionic/core/components'; +initialize(); + +document.querySelector('html')!.classList.add('ion-ce') + + +@Component({ + selector: 'app-root-standalone', + templateUrl: './app.component.html', + standalone: true, + imports: [RouterModule] +}) +export class AppComponent { +} diff --git a/packages/angular/test/base/src/app/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/app-standalone/app.routes.ts new file mode 100644 index 00000000000..3182d3167a2 --- /dev/null +++ b/packages/angular/test/base/src/app/app-standalone/app.routes.ts @@ -0,0 +1,13 @@ +import { Routes } from '@angular/router'; +import { AppComponent } from './app.component'; + +export const routes: Routes = [ + { + path: '', + component: AppComponent, + children: [ + { path: 'test', loadComponent: () => import('../test/test.component').then(m => m.TestComponent) }, + ] + }, +]; + diff --git a/packages/angular/test/base/src/app/app.component.css b/packages/angular/test/base/src/app/app.component.css deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/angular/test/base/src/app/app.component.html b/packages/angular/test/base/src/app/app.component.html index 13b96776ee1..0680b43f9c6 100644 --- a/packages/angular/test/base/src/app/app.component.html +++ b/packages/angular/test/base/src/app/app.component.html @@ -1,3 +1 @@ - - - + diff --git a/packages/angular/test/base/src/app/app.component.ts b/packages/angular/test/base/src/app/app.component.ts index 68478a86238..ead0de4491a 100644 --- a/packages/angular/test/base/src/app/app.component.ts +++ b/packages/angular/test/base/src/app/app.component.ts @@ -2,8 +2,7 @@ import { Component } from '@angular/core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] + templateUrl: './app.component.html' }) export class AppComponent { } diff --git a/packages/angular/test/base/src/app/app.module.ts b/packages/angular/test/base/src/app/app.module.ts index 908acfa0d0a..9d0a2a78eab 100644 --- a/packages/angular/test/base/src/app/app.module.ts +++ b/packages/angular/test/base/src/app/app.module.ts @@ -1,66 +1,27 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; -import { ReactiveFormsModule } from '@angular/forms'; import { RouteReuseStrategy } from '@angular/router'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; -import { FormsModule } from '@angular/forms'; -import { InputsComponent } from './inputs/inputs.component'; -import { ModalComponent } from './modal/modal.component'; -import { ModalExampleComponent } from './modal-example/modal-example.component'; -import { RouterLinkComponent } from './router-link/router-link.component'; -import { RouterLinkPageComponent } from './router-link-page/router-link-page.component'; -import { RouterLinkPage2Component } from './router-link-page2/router-link-page2.component'; -import { RouterLinkPage3Component } from './router-link-page3/router-link-page3.component'; -import { HomePageComponent } from './home-page/home-page.component'; -import { NestedOutletComponent } from './nested-outlet/nested-outlet.component'; -import { NestedOutletPageComponent } from './nested-outlet-page/nested-outlet-page.component'; -import { NestedOutletPage2Component } from './nested-outlet-page2/nested-outlet-page2.component'; -import { NavComponent } from './nav/nav.component'; -import { ViewChildComponent } from './view-child/view-child.component'; -import { ProvidersComponent } from './providers/providers.component'; -import { FormComponent } from './form/form.component'; -import { NavigationPage1Component } from './navigation-page1/navigation-page1.component'; -import { NavigationPage2Component } from './navigation-page2/navigation-page2.component'; -import { NavigationPage3Component } from './navigation-page3/navigation-page3.component'; -import { AlertComponent } from './alert/alert.component'; -import { AccordionComponent } from './accordion/accordion.component'; -import { AccordionModalComponent } from './accordion/accordion-modal/accordion-modal.component'; + +const isLazy = window.location.href.includes('lazy'); + +const imports = [ + BrowserModule.withServerTransition({ appId: 'serverApp' }), + AppRoutingModule, +]; + +if (isLazy) { + imports.push(IonicModule.forRoot({ keyboardHeight: 12345 })); +} @NgModule({ declarations: [ AppComponent, - InputsComponent, - ModalComponent, - ModalExampleComponent, - RouterLinkComponent, - RouterLinkPageComponent, - RouterLinkPage2Component, - RouterLinkPage3Component, - HomePageComponent, - NestedOutletComponent, - NestedOutletPageComponent, - NestedOutletPage2Component, - NavComponent, - ViewChildComponent, - ProvidersComponent, - FormComponent, - NavigationPage1Component, - NavigationPage2Component, - NavigationPage3Component, - AlertComponent, - AccordionComponent, - AccordionModalComponent - ], - imports: [ - BrowserModule.withServerTransition({ appId: 'serverApp' }), - AppRoutingModule, - FormsModule, - ReactiveFormsModule, - IonicModule.forRoot({ keyboardHeight: 12345 }), ], + imports, providers: [ { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, ], diff --git a/packages/angular/test/base/src/app/app.routes.ts b/packages/angular/test/base/src/app/app.routes.ts index 1d709b16bb1..f32930b100f 100644 --- a/packages/angular/test/base/src/app/app.routes.ts +++ b/packages/angular/test/base/src/app/app.routes.ts @@ -1,82 +1,16 @@ import { Routes } from '@angular/router'; -import { InputsComponent } from './inputs/inputs.component'; -import { ModalComponent } from './modal/modal.component'; -import { RouterLinkComponent } from './router-link/router-link.component'; -import { RouterLinkPageComponent } from './router-link-page/router-link-page.component'; -import { RouterLinkPage2Component } from './router-link-page2/router-link-page2.component'; -import { RouterLinkPage3Component } from './router-link-page3/router-link-page3.component'; -import { HomePageComponent } from './home-page/home-page.component'; -import { NestedOutletComponent } from './nested-outlet/nested-outlet.component'; -import { NestedOutletPageComponent } from './nested-outlet-page/nested-outlet-page.component'; -import { NestedOutletPage2Component } from './nested-outlet-page2/nested-outlet-page2.component'; -import { ViewChildComponent } from './view-child/view-child.component'; -import { ProvidersComponent } from './providers/providers.component'; -import { FormComponent } from './form/form.component'; -import { NavigationPage1Component } from './navigation-page1/navigation-page1.component'; -import { NavigationPage2Component } from './navigation-page2/navigation-page2.component'; -import { NavigationPage3Component } from './navigation-page3/navigation-page3.component'; -import { AlertComponent } from './alert/alert.component'; -import { AccordionComponent } from './accordion/accordion.component'; export const routes: Routes = [ - { path: '', component: HomePageComponent }, - { path: 'version-test', loadChildren: () => import('./version-test').then(m => m.VersionTestModule) }, - { path: 'accordions', component: AccordionComponent }, - { path: 'alerts', component: AlertComponent }, - { path: 'inputs', component: InputsComponent }, - { path: 'textarea', loadChildren: () => import('./textarea/textarea.module').then(m => m.TextareaModule) }, - { path: 'searchbar', loadChildren: () => import('./searchbar/searchbar.module').then(m => m.SearchbarModule) }, - { path: 'form', component: FormComponent }, - { path: 'modals', component: ModalComponent }, - { path: 'modal-inline', loadChildren: () => import('./modal-inline').then(m => m.ModalInlineModule) }, - { path: 'view-child', component: ViewChildComponent }, - { path: 'keep-contents-mounted', loadChildren: () => import('./keep-contents-mounted').then(m => m.OverlayAutoMountModule) }, - { path: 'overlays-inline', loadChildren: () => import('./overlays-inline').then(m => m.OverlaysInlineModule) }, - { path: 'popover-inline', loadChildren: () => import('./popover-inline').then(m => m.PopoverInlineModule) }, - { path: 'providers', component: ProvidersComponent }, - { path: 'router-link', component: RouterLinkComponent }, - { path: 'router-link-page', component: RouterLinkPageComponent }, - { path: 'router-link-page2/:id', component: RouterLinkPage2Component }, - { path: 'router-link-page3', component: RouterLinkPage3Component }, - { path: 'standalone', loadComponent: () => import('./standalone/standalone.component').then(c => c.StandaloneComponent) }, - { path: 'tabs', redirectTo: '/tabs/account', pathMatch: 'full' }, { - path: 'navigation', - children: [ - { path: 'page1', component: NavigationPage1Component }, - { path: 'page2', component: NavigationPage2Component }, - { path: 'page3', component: NavigationPage3Component } - ] + path: '', + pathMatch: 'full', + /** + * Omit the slash at the beginning + * so query params are preserved. + * https://github.com/angular/angular/issues/13315#issuecomment-427254639 + */ + redirectTo: 'lazy' }, - { - path: 'tabs', - loadChildren: () => import('./tabs/tabs.module').then(m => m.TabsPageModule) - }, - { - path: 'tabs-global', - loadChildren: () => import('./tabs-global/tabs-global.module').then(m => m.TabsGlobalModule) - }, - { - path: 'tabs-slots', - loadComponent: () => import('./tabs-slots.component').then(c => c.TabsSlotsComponent) - }, - { - path: 'nested-outlet', - component: NestedOutletComponent, - children: [ - { - path: 'page', - component: NestedOutletPageComponent - }, - { - path: 'page2', - component: NestedOutletPage2Component - } - ] - }, - { - path: 'form-controls/range', - loadChildren: () => import('./form-controls/range/range.module').then(m => m.RangeModule) - } + { path: 'lazy', loadChildren: () => import('./app-lazy/app.module').then(m => m.AppModule) }, + { path: 'standalone', loadChildren: () => import('./app-standalone/app.routes').then(m => m.routes) } ]; - diff --git a/packages/angular/test/base/src/app/home-page/home-page.component.html b/packages/angular/test/base/src/app/home-page/home-page.component.html index 6f3cf17b835..314f6e6ae68 100644 --- a/packages/angular/test/base/src/app/home-page/home-page.component.html +++ b/packages/angular/test/base/src/app/home-page/home-page.component.html @@ -7,52 +7,52 @@ - + Alerts test - + Inputs test - + Form test - + Modals test - + Router link test - + Tabs test - + Nested ion-router-outlet - + ViewChild() - + Providers - + Accordions Test diff --git a/packages/angular/test/base/src/app/navigation-page1/navigation-page1.component.ts b/packages/angular/test/base/src/app/navigation-page1/navigation-page1.component.ts index da3d7184959..aa20a264729 100644 --- a/packages/angular/test/base/src/app/navigation-page1/navigation-page1.component.ts +++ b/packages/angular/test/base/src/app/navigation-page1/navigation-page1.component.ts @@ -13,7 +13,7 @@ export class NavigationPage1Component { ionViewDidEnter() { if (count < 1) { - this.navController.navigateBack('/navigation/page2'); + this.navController.navigateBack('/lazy/navigation/page2'); } count++; } diff --git a/packages/angular/test/base/src/app/navigation-page2/navigation-page2.component.ts b/packages/angular/test/base/src/app/navigation-page2/navigation-page2.component.ts index d24c4eb9eb9..b8534a5f615 100644 --- a/packages/angular/test/base/src/app/navigation-page2/navigation-page2.component.ts +++ b/packages/angular/test/base/src/app/navigation-page2/navigation-page2.component.ts @@ -12,6 +12,6 @@ export class NavigationPage2Component { ) {} ionViewDidEnter() { - this.navController.navigateForward('/navigation/page1'); + this.navController.navigateForward('/lazy/navigation/page1'); } } diff --git a/packages/angular/test/base/src/app/navigation-page3/navigation-page3.component.ts b/packages/angular/test/base/src/app/navigation-page3/navigation-page3.component.ts index 24fe4c10f43..c467798c2e3 100644 --- a/packages/angular/test/base/src/app/navigation-page3/navigation-page3.component.ts +++ b/packages/angular/test/base/src/app/navigation-page3/navigation-page3.component.ts @@ -11,6 +11,6 @@ export class NavigationPage3Component { ) {} ionViewDidEnter() { - this.navController.navigateRoot('/navigation/page2'); + this.navController.navigateRoot('/lazy/navigation/page2'); } } diff --git a/packages/angular/test/base/src/app/nested-outlet-page/nested-outlet-page.component.html b/packages/angular/test/base/src/app/nested-outlet-page/nested-outlet-page.component.html index c599655a8a7..10d40ff3242 100644 --- a/packages/angular/test/base/src/app/nested-outlet-page/nested-outlet-page.component.html +++ b/packages/angular/test/base/src/app/nested-outlet-page/nested-outlet-page.component.html @@ -1,7 +1,7 @@

Nested page 1

- Go To Tabs - Go To SECOND + Go To Tabs + Go To SECOND

diff --git a/packages/angular/test/base/src/app/nested-outlet-page2/nested-outlet-page2.component.html b/packages/angular/test/base/src/app/nested-outlet-page2/nested-outlet-page2.component.html index 5b857a378d1..02d834d8db7 100644 --- a/packages/angular/test/base/src/app/nested-outlet-page2/nested-outlet-page2.component.html +++ b/packages/angular/test/base/src/app/nested-outlet-page2/nested-outlet-page2.component.html @@ -1,6 +1,6 @@

Nested page 2

- Go To FIRST + Go To FIRST

diff --git a/packages/angular/test/base/src/app/router-link-page2/router-link-page2.component.html b/packages/angular/test/base/src/app/router-link-page2/router-link-page2.component.html index b78d4288ddf..e63979f3716 100644 --- a/packages/angular/test/base/src/app/router-link-page2/router-link-page2.component.html +++ b/packages/angular/test/base/src/app/router-link-page2/router-link-page2.component.html @@ -8,5 +8,5 @@ - Go to Page 3 - \ No newline at end of file + Go to Page 3 +
diff --git a/packages/angular/test/base/src/app/router-link/router-link.component.html b/packages/angular/test/base/src/app/router-link/router-link.component.html index 514b1a546fd..791b6a54424 100644 --- a/packages/angular/test/base/src/app/router-link/router-link.component.html +++ b/packages/angular/test/base/src/app/router-link/router-link.component.html @@ -15,25 +15,25 @@

Change Detections: {{counter()}}

- ion-button[routerLink] + ion-button[routerLink] - + ion-button[routerLink] (direction:root) - + ion-button[routerLink] (direction:back)

null router link

-

a[routerLink]

-

a[routerLink] (direction:root)

-

a[routerLink] (direction:back)

+

a[routerLink]

+

a[routerLink] (direction:root)

+

a[routerLink] (direction:back)

-

diff --git a/packages/angular/test/base/src/app/router-link/router-link.component.ts b/packages/angular/test/base/src/app/router-link/router-link.component.ts index ac771a78e9a..8ce952bbb0e 100644 --- a/packages/angular/test/base/src/app/router-link/router-link.component.ts +++ b/packages/angular/test/base/src/app/router-link/router-link.component.ts @@ -21,19 +21,19 @@ export class RouterLinkComponent implements OnInit, ViewWillEnter, ViewDidEnter, ) {} navigate() { - this.router.navigateByUrl('/router-link-page'); + this.router.navigateByUrl('/lazy/router-link-page'); } navigateForward() { - this.navCtrl.navigateForward('/router-link-page'); + this.navCtrl.navigateForward('/lazy/router-link-page'); } navigateBack() { - this.navCtrl.navigateBack('/router-link-page'); + this.navCtrl.navigateBack('/lazy/router-link-page'); } navigateRoot() { - this.navCtrl.navigateRoot('/router-link-page'); + this.navCtrl.navigateRoot('/lazy/router-link-page'); } counter() { diff --git a/packages/angular/test/base/src/app/standalone/standalone.component.html b/packages/angular/test/base/src/app/standalone/standalone.component.html index c5f470a160a..aea664b4048 100644 --- a/packages/angular/test/base/src/app/standalone/standalone.component.html +++ b/packages/angular/test/base/src/app/standalone/standalone.component.html @@ -1,4 +1,4 @@

This is a standalone component rendered from a route.

- Return home + Return home
diff --git a/packages/angular/test/base/src/app/tabs-lazy/tabs-tab3-nested/tabs-tab3-nested.component.html b/packages/angular/test/base/src/app/tabs-lazy/tabs-tab3-nested/tabs-tab3-nested.component.html index 54c0cb5c386..00bf0bd5a00 100644 --- a/packages/angular/test/base/src/app/tabs-lazy/tabs-tab3-nested/tabs-tab3-nested.component.html +++ b/packages/angular/test/base/src/app/tabs-lazy/tabs-tab3-nested/tabs-tab3-nested.component.html @@ -2,14 +2,14 @@ Tab 3 - Page 2 - +

- Go to Tab 1 - Page 1 - Go to Tab 1 - Page 2 + Go to Tab 1 - Page 1 + Go to Tab 1 - Page 2

diff --git a/packages/angular/test/base/src/app/tabs-lazy/tabs-tab3/tabs-tab3.component.html b/packages/angular/test/base/src/app/tabs-lazy/tabs-tab3/tabs-tab3.component.html index b28bbd0a3df..e72bf52cde9 100644 --- a/packages/angular/test/base/src/app/tabs-lazy/tabs-tab3/tabs-tab3.component.html +++ b/packages/angular/test/base/src/app/tabs-lazy/tabs-tab3/tabs-tab3.component.html @@ -7,9 +7,9 @@

LAZY LOADED TAB

- Go to Tab 1 - Page 1 - Go to Tab 1 - Page 2 - Go to Tab 3 - Page 2 + Go to Tab 1 - Page 1 + Go to Tab 1 - Page 2 + Go to Tab 3 - Page 2

diff --git a/packages/angular/test/base/src/app/tabs-tab1-nested/tabs-tab1-nested.component.html b/packages/angular/test/base/src/app/tabs-tab1-nested/tabs-tab1-nested.component.html index 8c3980c956e..f6b945b8e05 100644 --- a/packages/angular/test/base/src/app/tabs-tab1-nested/tabs-tab1-nested.component.html +++ b/packages/angular/test/base/src/app/tabs-tab1-nested/tabs-tab1-nested.component.html @@ -10,11 +10,11 @@

Welcome to NESTED PAGE {{id}}

- Go to Tab 1 - Page 1 - Go to Tab 2 - Page 1 - Go to Global Page - Go to Previous Page (NavController). + Go to Tab 1 - Page 1 + Go to Tab 2 - Page 1 + Go to Global Page + Go to Previous Page (NavController). - Go to Next + Go to Next

diff --git a/packages/angular/test/base/src/app/tabs-tab1/tabs-tab1.component.html b/packages/angular/test/base/src/app/tabs-tab1/tabs-tab1.component.html index c9aefba4ee4..af90d676012 100644 --- a/packages/angular/test/base/src/app/tabs-tab1/tabs-tab1.component.html +++ b/packages/angular/test/base/src/app/tabs-tab1/tabs-tab1.component.html @@ -18,11 +18,11 @@

Welcome to Tab1

Segment changed: {{changed}}

- Go to Page 2 - Go to Page 2 + Go to Page 2 with Query Params - Go to Tab 3 - Page 2 - Go to nested + Go to Tab 3 - Page 2 + Go to nested Go to Previous Page

diff --git a/packages/angular/test/base/src/app/tabs-tab2/tabs-tab2.component.html b/packages/angular/test/base/src/app/tabs-tab2/tabs-tab2.component.html index 93853ab16dc..23ac5cf6d5b 100644 --- a/packages/angular/test/base/src/app/tabs-tab2/tabs-tab2.component.html +++ b/packages/angular/test/base/src/app/tabs-tab2/tabs-tab2.component.html @@ -18,8 +18,8 @@

Welcome to Tab 2

Segment changed: {{changed}}

- Go to Tab 1 - Page 1 - Go to Tab 1 - Page 2 - Go to nested + Go to Tab 1 - Page 1 + Go to Tab 1 - Page 2 + Go to nested

diff --git a/packages/angular/test/base/src/app/test/test.component.html b/packages/angular/test/base/src/app/test/test.component.html new file mode 100644 index 00000000000..0527e6bd2d7 --- /dev/null +++ b/packages/angular/test/base/src/app/test/test.component.html @@ -0,0 +1 @@ +This is a test diff --git a/packages/angular/test/base/src/app/test/test.component.ts b/packages/angular/test/base/src/app/test/test.component.ts new file mode 100644 index 00000000000..081b603159c --- /dev/null +++ b/packages/angular/test/base/src/app/test/test.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-test', + templateUrl: './test.component.html', + standalone: true +}) +export class TestComponent { + constructor() { + console.log('This should be undefined:', customElements.get('ion-app')); + } +} From 291b1310a600e440bf06a2005bf2a18e7a92b98b Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Tue, 1 Aug 2023 15:44:26 -0400 Subject: [PATCH 03/24] chore: move lazy and standalone tests to subdirectories (#27881) --- .../ng14/e2e/src/{ => lazy}/angular-version.spec.ts | 0 .../version-test/version-test-routing.module.ts | 0 .../ng15/e2e/src/{ => lazy}/angular-version.spec.ts | 0 .../version-test/version-test-routing.module.ts | 0 .../ng16/e2e/src/{ => lazy}/angular-version.spec.ts | 0 .../e2e/src/{ => lazy}/bind-component-inputs.spec.ts | 0 .../ng16/e2e/src/{ => lazy}/modal-nav-params.spec.ts | 0 .../bind-component-inputs.component.ts | 0 .../modal-nav-params/modal-nav-params.component.ts | 0 .../modal-nav-params/nav-root.component.ts | 0 .../version-test/version-test-routing.module.ts | 0 .../test/base/e2e/src/{ => lazy}/accordion.spec.ts | 0 .../e2e/src/{ => lazy}/form-controls/range.spec.ts | 0 .../test/base/e2e/src/{ => lazy}/form.spec.ts | 0 .../base/e2e/src/{ => lazy}/inline-overlays.spec.ts | 0 .../test/base/e2e/src/{ => lazy}/inputs.spec.ts | 0 .../e2e/src/{ => lazy}/keep-contents-mounted.spec.ts | 0 .../test/base/e2e/src/{ => lazy}/modal.spec.ts | 0 .../test/base/e2e/src/{ => lazy}/navigation.spec.ts | 0 .../base/e2e/src/{ => lazy}/nested-outlet.spec.ts | 0 .../test/base/e2e/src/{ => lazy}/popover.spec.ts | 0 .../test/base/e2e/src/{ => lazy}/providers.spec.ts | 0 .../test/base/e2e/src/{ => lazy}/router-link.spec.ts | 0 .../test/base/e2e/src/{ => lazy}/routing.spec.ts | 0 .../test/base/e2e/src/{ => lazy}/searchbar.spec.ts | 0 .../e2e/src/{ => lazy}/standalone-routing.spec.ts | 0 .../test/base/e2e/src/{ => lazy}/tabs.spec.ts | 0 .../test/base/e2e/src/{ => lazy}/textarea.spec.ts | 0 .../test/base/e2e/src/{ => lazy}/view-child.spec.ts | 0 packages/angular/test/base/src/app/app.routes.ts | 4 ++-- .../accordion-modal/accordion-modal.component.html | 0 .../accordion-modal/accordion-modal.component.ts | 0 .../{ => lazy}/accordion/accordion.component.html | 0 .../app/{ => lazy}/accordion/accordion.component.ts | 0 .../src/app/{ => lazy}/alert/alert.component.html | 0 .../base/src/app/{ => lazy}/alert/alert.component.ts | 0 .../app/{ => lazy}/app-lazy/app-routing.module.ts | 0 .../src/app/{ => lazy}/app-lazy/app.component.html | 0 .../src/app/{ => lazy}/app-lazy/app.component.ts | 0 .../base/src/app/{ => lazy}/app-lazy/app.module.ts | 0 .../base/src/app/{ => lazy}/app-lazy/app.routes.ts | 2 +- .../form-controls/range/range-routing.module.ts | 0 .../form-controls/range/range.component.html | 0 .../form-controls/range/range.component.ts | 0 .../{ => lazy}/form-controls/range/range.module.ts | 0 .../base/src/app/{ => lazy}/form/form.component.html | 0 .../base/src/app/{ => lazy}/form/form.component.ts | 0 .../{ => lazy}/home-page/home-page.component.html | 0 .../app/{ => lazy}/home-page/home-page.component.ts | 0 .../src/app/{ => lazy}/inputs/inputs.component.html | 0 .../src/app/{ => lazy}/inputs/inputs.component.ts | 0 .../app/{ => lazy}/keep-contents-mounted/index.ts | 0 .../keep-contents-mounted-routing.module.ts | 0 .../keep-contents-mounted.component.html | 0 .../keep-contents-mounted.component.ts | 0 .../keep-contents-mounted.module.ts | 0 .../modal-example/modal-example.component.html | 0 .../modal-example/modal-example.component.ts | 0 .../base/src/app/{ => lazy}/modal-inline/index.ts | 0 .../modal-inline/modal-inline-routing.module.ts | 0 .../modal-inline/modal-inline.component.html | 0 .../modal-inline/modal-inline.component.ts | 0 .../{ => lazy}/modal-inline/modal-inline.module.ts | 0 .../src/app/{ => lazy}/modal/modal.component.html | 0 .../base/src/app/{ => lazy}/modal/modal.component.ts | 0 .../base/src/app/{ => lazy}/nav/nav.component.html | 0 .../base/src/app/{ => lazy}/nav/nav.component.ts | 0 .../navigation-page1/navigation-page1.component.html | 0 .../navigation-page1/navigation-page1.component.ts | 0 .../navigation-page2/navigation-page2.component.html | 0 .../navigation-page2/navigation-page2.component.ts | 0 .../navigation-page3/navigation-page3.component.html | 0 .../navigation-page3/navigation-page3.component.ts | 0 .../nested-outlet-page.component.html | 0 .../nested-outlet-page.component.ts | 0 .../nested-outlet-page2.component.html | 0 .../nested-outlet-page2.component.ts | 0 .../nested-outlet/nested-outlet.component.html | 0 .../nested-outlet/nested-outlet.component.ts | 0 .../base/src/app/{ => lazy}/overlays-inline/index.ts | 0 .../overlays-inline-routing.module.ts | 0 .../overlays-inline/overlays-inline.component.html | 0 .../overlays-inline/overlays-inline.component.ts | 0 .../overlays-inline/overlays-inline.module.ts | 0 .../base/src/app/{ => lazy}/popover-inline/index.ts | 0 .../popover-inline/popover-inline-routing.module.ts | 0 .../popover-inline/popover-inline.component.html | 0 .../popover-inline/popover-inline.component.ts | 0 .../popover-inline/popover-inline.module.ts | 0 .../{ => lazy}/providers/providers.component.html | 0 .../app/{ => lazy}/providers/providers.component.ts | 0 .../router-link-page/router-link-page.component.html | 0 .../router-link-page/router-link-page.component.ts | 0 .../router-link-page2.component.html | 0 .../router-link-page2/router-link-page2.component.ts | 0 .../router-link-page3.component.html | 0 .../router-link-page3/router-link-page3.component.ts | 0 .../router-link/router-link.component.html | 0 .../{ => lazy}/router-link/router-link.component.ts | 0 .../routing-standalone/standalone.component.html | 4 ++++ .../lazy/routing-standalone/standalone.component.ts | 12 ++++++++++++ .../{ => lazy}/searchbar/searchbar-routing.module.ts | 0 .../{ => lazy}/searchbar/searchbar.component.html | 0 .../app/{ => lazy}/searchbar/searchbar.component.ts | 0 .../src/app/{ => lazy}/searchbar/searchbar.module.ts | 0 .../src/app/{ => lazy}/slides/slides.component.ts | 0 .../tabs-global/tabs-global-routing.module.ts | 0 .../tabs-global/tabs-global.component.html | 0 .../{ => lazy}/tabs-global/tabs-global.component.ts | 0 .../app/{ => lazy}/tabs-global/tabs-global.module.ts | 0 .../{ => lazy}/tabs-lazy/tabs-lazy-routing.module.ts | 0 .../src/app/{ => lazy}/tabs-lazy/tabs-lazy.module.ts | 0 .../tabs-tab3-nested/tabs-tab3-nested.component.html | 0 .../tabs-tab3-nested/tabs-tab3-nested.component.ts | 0 .../tabs-lazy/tabs-tab3/tabs-tab3.component.html | 0 .../tabs-lazy/tabs-tab3/tabs-tab3.component.ts | 0 .../base/src/app/{ => lazy}/tabs-slots.component.ts | 0 .../tabs-tab1-nested/tabs-tab1-nested.component.html | 0 .../tabs-tab1-nested/tabs-tab1-nested.component.ts | 0 .../{ => lazy}/tabs-tab1/tabs-tab1.component.html | 0 .../app/{ => lazy}/tabs-tab1/tabs-tab1.component.ts | 0 .../{ => lazy}/tabs-tab2/tabs-tab2.component.html | 0 .../app/{ => lazy}/tabs-tab2/tabs-tab2.component.ts | 0 .../base/src/app/{ => lazy}/tabs/tabs.component.css | 0 .../base/src/app/{ => lazy}/tabs/tabs.component.html | 0 .../base/src/app/{ => lazy}/tabs/tabs.component.ts | 0 .../test/base/src/app/{ => lazy}/tabs/tabs.module.ts | 0 .../src/app/{ => lazy}/tabs/tabs.router.module.ts | 0 .../{ => lazy}/textarea/textarea-routing.module.ts | 0 .../app/{ => lazy}/textarea/textarea.component.html | 0 .../app/{ => lazy}/textarea/textarea.component.ts | 0 .../src/app/{ => lazy}/textarea/textarea.module.ts | 0 .../base/src/app/{ => lazy}/version-test/index.ts | 0 .../version-test/version-test-routing.module.ts | 0 .../version-test/version-test.component.html | 0 .../version-test/version-test.component.ts | 0 .../{ => lazy}/version-test/version-test.module.ts | 0 .../{ => lazy}/view-child/view-child.component.html | 0 .../{ => lazy}/view-child/view-child.component.ts | 0 .../virtual-scroll-detail.component.ts | 0 .../virtual-scroll-inner.component.ts | 0 .../virtual-scroll/virtual-scroll.component.ts | 0 .../app-standalone/app.component.html | 0 .../{ => standalone}/app-standalone/app.component.ts | 0 .../{ => standalone}/app-standalone/app.routes.ts | 0 .../app/{ => standalone}/test/test.component.html | 0 .../src/app/{ => standalone}/test/test.component.ts | 0 147 files changed, 19 insertions(+), 3 deletions(-) rename packages/angular/test/apps/ng14/e2e/src/{ => lazy}/angular-version.spec.ts (100%) rename packages/angular/test/apps/ng14/src/app/{ => lazy}/version-test/version-test-routing.module.ts (100%) rename packages/angular/test/apps/ng15/e2e/src/{ => lazy}/angular-version.spec.ts (100%) rename packages/angular/test/apps/ng15/src/app/{ => lazy}/version-test/version-test-routing.module.ts (100%) rename packages/angular/test/apps/ng16/e2e/src/{ => lazy}/angular-version.spec.ts (100%) rename packages/angular/test/apps/ng16/e2e/src/{ => lazy}/bind-component-inputs.spec.ts (100%) rename packages/angular/test/apps/ng16/e2e/src/{ => lazy}/modal-nav-params.spec.ts (100%) rename packages/angular/test/apps/ng16/src/app/{ => lazy}/version-test/bind-component-inputs/bind-component-inputs.component.ts (100%) rename packages/angular/test/apps/ng16/src/app/{ => lazy}/version-test/modal-nav-params/modal-nav-params.component.ts (100%) rename packages/angular/test/apps/ng16/src/app/{ => lazy}/version-test/modal-nav-params/nav-root.component.ts (100%) rename packages/angular/test/apps/ng16/src/app/{ => lazy}/version-test/version-test-routing.module.ts (100%) rename packages/angular/test/base/e2e/src/{ => lazy}/accordion.spec.ts (100%) rename packages/angular/test/base/e2e/src/{ => lazy}/form-controls/range.spec.ts (100%) rename packages/angular/test/base/e2e/src/{ => lazy}/form.spec.ts (100%) rename packages/angular/test/base/e2e/src/{ => lazy}/inline-overlays.spec.ts (100%) rename packages/angular/test/base/e2e/src/{ => lazy}/inputs.spec.ts (100%) rename packages/angular/test/base/e2e/src/{ => lazy}/keep-contents-mounted.spec.ts (100%) rename packages/angular/test/base/e2e/src/{ => lazy}/modal.spec.ts (100%) rename packages/angular/test/base/e2e/src/{ => lazy}/navigation.spec.ts (100%) rename packages/angular/test/base/e2e/src/{ => lazy}/nested-outlet.spec.ts (100%) rename packages/angular/test/base/e2e/src/{ => lazy}/popover.spec.ts (100%) rename packages/angular/test/base/e2e/src/{ => lazy}/providers.spec.ts (100%) rename packages/angular/test/base/e2e/src/{ => lazy}/router-link.spec.ts (100%) rename packages/angular/test/base/e2e/src/{ => lazy}/routing.spec.ts (100%) rename packages/angular/test/base/e2e/src/{ => lazy}/searchbar.spec.ts (100%) rename packages/angular/test/base/e2e/src/{ => lazy}/standalone-routing.spec.ts (100%) rename packages/angular/test/base/e2e/src/{ => lazy}/tabs.spec.ts (100%) rename packages/angular/test/base/e2e/src/{ => lazy}/textarea.spec.ts (100%) rename packages/angular/test/base/e2e/src/{ => lazy}/view-child.spec.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/accordion/accordion-modal/accordion-modal.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/accordion/accordion-modal/accordion-modal.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/accordion/accordion.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/accordion/accordion.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/alert/alert.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/alert/alert.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/app-lazy/app-routing.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/app-lazy/app.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/app-lazy/app.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/app-lazy/app.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/app-lazy/app.routes.ts (96%) rename packages/angular/test/base/src/app/{ => lazy}/form-controls/range/range-routing.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/form-controls/range/range.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/form-controls/range/range.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/form-controls/range/range.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/form/form.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/form/form.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/home-page/home-page.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/home-page/home-page.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/inputs/inputs.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/inputs/inputs.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/keep-contents-mounted/index.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/keep-contents-mounted/keep-contents-mounted-routing.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/keep-contents-mounted/keep-contents-mounted.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/keep-contents-mounted/keep-contents-mounted.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/keep-contents-mounted/keep-contents-mounted.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/modal-example/modal-example.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/modal-example/modal-example.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/modal-inline/index.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/modal-inline/modal-inline-routing.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/modal-inline/modal-inline.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/modal-inline/modal-inline.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/modal-inline/modal-inline.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/modal/modal.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/modal/modal.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/nav/nav.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/nav/nav.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/navigation-page1/navigation-page1.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/navigation-page1/navigation-page1.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/navigation-page2/navigation-page2.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/navigation-page2/navigation-page2.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/navigation-page3/navigation-page3.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/navigation-page3/navigation-page3.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/nested-outlet-page/nested-outlet-page.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/nested-outlet-page/nested-outlet-page.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/nested-outlet-page2/nested-outlet-page2.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/nested-outlet-page2/nested-outlet-page2.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/nested-outlet/nested-outlet.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/nested-outlet/nested-outlet.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/overlays-inline/index.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/overlays-inline/overlays-inline-routing.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/overlays-inline/overlays-inline.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/overlays-inline/overlays-inline.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/overlays-inline/overlays-inline.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/popover-inline/index.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/popover-inline/popover-inline-routing.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/popover-inline/popover-inline.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/popover-inline/popover-inline.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/popover-inline/popover-inline.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/providers/providers.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/providers/providers.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/router-link-page/router-link-page.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/router-link-page/router-link-page.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/router-link-page2/router-link-page2.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/router-link-page2/router-link-page2.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/router-link-page3/router-link-page3.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/router-link-page3/router-link-page3.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/router-link/router-link.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/router-link/router-link.component.ts (100%) create mode 100644 packages/angular/test/base/src/app/lazy/routing-standalone/standalone.component.html create mode 100644 packages/angular/test/base/src/app/lazy/routing-standalone/standalone.component.ts rename packages/angular/test/base/src/app/{ => lazy}/searchbar/searchbar-routing.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/searchbar/searchbar.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/searchbar/searchbar.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/searchbar/searchbar.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/slides/slides.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs-global/tabs-global-routing.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs-global/tabs-global.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs-global/tabs-global.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs-global/tabs-global.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs-lazy/tabs-lazy-routing.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs-lazy/tabs-lazy.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs-lazy/tabs-tab3-nested/tabs-tab3-nested.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs-lazy/tabs-tab3-nested/tabs-tab3-nested.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs-lazy/tabs-tab3/tabs-tab3.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs-lazy/tabs-tab3/tabs-tab3.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs-slots.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs-tab1-nested/tabs-tab1-nested.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs-tab1-nested/tabs-tab1-nested.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs-tab1/tabs-tab1.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs-tab1/tabs-tab1.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs-tab2/tabs-tab2.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs-tab2/tabs-tab2.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs/tabs.component.css (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs/tabs.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs/tabs.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs/tabs.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/tabs/tabs.router.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/textarea/textarea-routing.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/textarea/textarea.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/textarea/textarea.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/textarea/textarea.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/version-test/index.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/version-test/version-test-routing.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/version-test/version-test.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/version-test/version-test.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/version-test/version-test.module.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/view-child/view-child.component.html (100%) rename packages/angular/test/base/src/app/{ => lazy}/view-child/view-child.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/virtual-scroll-detail/virtual-scroll-detail.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/virtual-scroll-inner/virtual-scroll-inner.component.ts (100%) rename packages/angular/test/base/src/app/{ => lazy}/virtual-scroll/virtual-scroll.component.ts (100%) rename packages/angular/test/base/src/app/{ => standalone}/app-standalone/app.component.html (100%) rename packages/angular/test/base/src/app/{ => standalone}/app-standalone/app.component.ts (100%) rename packages/angular/test/base/src/app/{ => standalone}/app-standalone/app.routes.ts (100%) rename packages/angular/test/base/src/app/{ => standalone}/test/test.component.html (100%) rename packages/angular/test/base/src/app/{ => standalone}/test/test.component.ts (100%) diff --git a/packages/angular/test/apps/ng14/e2e/src/angular-version.spec.ts b/packages/angular/test/apps/ng14/e2e/src/lazy/angular-version.spec.ts similarity index 100% rename from packages/angular/test/apps/ng14/e2e/src/angular-version.spec.ts rename to packages/angular/test/apps/ng14/e2e/src/lazy/angular-version.spec.ts diff --git a/packages/angular/test/apps/ng14/src/app/version-test/version-test-routing.module.ts b/packages/angular/test/apps/ng14/src/app/lazy/version-test/version-test-routing.module.ts similarity index 100% rename from packages/angular/test/apps/ng14/src/app/version-test/version-test-routing.module.ts rename to packages/angular/test/apps/ng14/src/app/lazy/version-test/version-test-routing.module.ts diff --git a/packages/angular/test/apps/ng15/e2e/src/angular-version.spec.ts b/packages/angular/test/apps/ng15/e2e/src/lazy/angular-version.spec.ts similarity index 100% rename from packages/angular/test/apps/ng15/e2e/src/angular-version.spec.ts rename to packages/angular/test/apps/ng15/e2e/src/lazy/angular-version.spec.ts diff --git a/packages/angular/test/apps/ng15/src/app/version-test/version-test-routing.module.ts b/packages/angular/test/apps/ng15/src/app/lazy/version-test/version-test-routing.module.ts similarity index 100% rename from packages/angular/test/apps/ng15/src/app/version-test/version-test-routing.module.ts rename to packages/angular/test/apps/ng15/src/app/lazy/version-test/version-test-routing.module.ts diff --git a/packages/angular/test/apps/ng16/e2e/src/angular-version.spec.ts b/packages/angular/test/apps/ng16/e2e/src/lazy/angular-version.spec.ts similarity index 100% rename from packages/angular/test/apps/ng16/e2e/src/angular-version.spec.ts rename to packages/angular/test/apps/ng16/e2e/src/lazy/angular-version.spec.ts diff --git a/packages/angular/test/apps/ng16/e2e/src/bind-component-inputs.spec.ts b/packages/angular/test/apps/ng16/e2e/src/lazy/bind-component-inputs.spec.ts similarity index 100% rename from packages/angular/test/apps/ng16/e2e/src/bind-component-inputs.spec.ts rename to packages/angular/test/apps/ng16/e2e/src/lazy/bind-component-inputs.spec.ts diff --git a/packages/angular/test/apps/ng16/e2e/src/modal-nav-params.spec.ts b/packages/angular/test/apps/ng16/e2e/src/lazy/modal-nav-params.spec.ts similarity index 100% rename from packages/angular/test/apps/ng16/e2e/src/modal-nav-params.spec.ts rename to packages/angular/test/apps/ng16/e2e/src/lazy/modal-nav-params.spec.ts diff --git a/packages/angular/test/apps/ng16/src/app/version-test/bind-component-inputs/bind-component-inputs.component.ts b/packages/angular/test/apps/ng16/src/app/lazy/version-test/bind-component-inputs/bind-component-inputs.component.ts similarity index 100% rename from packages/angular/test/apps/ng16/src/app/version-test/bind-component-inputs/bind-component-inputs.component.ts rename to packages/angular/test/apps/ng16/src/app/lazy/version-test/bind-component-inputs/bind-component-inputs.component.ts diff --git a/packages/angular/test/apps/ng16/src/app/version-test/modal-nav-params/modal-nav-params.component.ts b/packages/angular/test/apps/ng16/src/app/lazy/version-test/modal-nav-params/modal-nav-params.component.ts similarity index 100% rename from packages/angular/test/apps/ng16/src/app/version-test/modal-nav-params/modal-nav-params.component.ts rename to packages/angular/test/apps/ng16/src/app/lazy/version-test/modal-nav-params/modal-nav-params.component.ts diff --git a/packages/angular/test/apps/ng16/src/app/version-test/modal-nav-params/nav-root.component.ts b/packages/angular/test/apps/ng16/src/app/lazy/version-test/modal-nav-params/nav-root.component.ts similarity index 100% rename from packages/angular/test/apps/ng16/src/app/version-test/modal-nav-params/nav-root.component.ts rename to packages/angular/test/apps/ng16/src/app/lazy/version-test/modal-nav-params/nav-root.component.ts diff --git a/packages/angular/test/apps/ng16/src/app/version-test/version-test-routing.module.ts b/packages/angular/test/apps/ng16/src/app/lazy/version-test/version-test-routing.module.ts similarity index 100% rename from packages/angular/test/apps/ng16/src/app/version-test/version-test-routing.module.ts rename to packages/angular/test/apps/ng16/src/app/lazy/version-test/version-test-routing.module.ts diff --git a/packages/angular/test/base/e2e/src/accordion.spec.ts b/packages/angular/test/base/e2e/src/lazy/accordion.spec.ts similarity index 100% rename from packages/angular/test/base/e2e/src/accordion.spec.ts rename to packages/angular/test/base/e2e/src/lazy/accordion.spec.ts diff --git a/packages/angular/test/base/e2e/src/form-controls/range.spec.ts b/packages/angular/test/base/e2e/src/lazy/form-controls/range.spec.ts similarity index 100% rename from packages/angular/test/base/e2e/src/form-controls/range.spec.ts rename to packages/angular/test/base/e2e/src/lazy/form-controls/range.spec.ts diff --git a/packages/angular/test/base/e2e/src/form.spec.ts b/packages/angular/test/base/e2e/src/lazy/form.spec.ts similarity index 100% rename from packages/angular/test/base/e2e/src/form.spec.ts rename to packages/angular/test/base/e2e/src/lazy/form.spec.ts diff --git a/packages/angular/test/base/e2e/src/inline-overlays.spec.ts b/packages/angular/test/base/e2e/src/lazy/inline-overlays.spec.ts similarity index 100% rename from packages/angular/test/base/e2e/src/inline-overlays.spec.ts rename to packages/angular/test/base/e2e/src/lazy/inline-overlays.spec.ts diff --git a/packages/angular/test/base/e2e/src/inputs.spec.ts b/packages/angular/test/base/e2e/src/lazy/inputs.spec.ts similarity index 100% rename from packages/angular/test/base/e2e/src/inputs.spec.ts rename to packages/angular/test/base/e2e/src/lazy/inputs.spec.ts diff --git a/packages/angular/test/base/e2e/src/keep-contents-mounted.spec.ts b/packages/angular/test/base/e2e/src/lazy/keep-contents-mounted.spec.ts similarity index 100% rename from packages/angular/test/base/e2e/src/keep-contents-mounted.spec.ts rename to packages/angular/test/base/e2e/src/lazy/keep-contents-mounted.spec.ts diff --git a/packages/angular/test/base/e2e/src/modal.spec.ts b/packages/angular/test/base/e2e/src/lazy/modal.spec.ts similarity index 100% rename from packages/angular/test/base/e2e/src/modal.spec.ts rename to packages/angular/test/base/e2e/src/lazy/modal.spec.ts diff --git a/packages/angular/test/base/e2e/src/navigation.spec.ts b/packages/angular/test/base/e2e/src/lazy/navigation.spec.ts similarity index 100% rename from packages/angular/test/base/e2e/src/navigation.spec.ts rename to packages/angular/test/base/e2e/src/lazy/navigation.spec.ts diff --git a/packages/angular/test/base/e2e/src/nested-outlet.spec.ts b/packages/angular/test/base/e2e/src/lazy/nested-outlet.spec.ts similarity index 100% rename from packages/angular/test/base/e2e/src/nested-outlet.spec.ts rename to packages/angular/test/base/e2e/src/lazy/nested-outlet.spec.ts diff --git a/packages/angular/test/base/e2e/src/popover.spec.ts b/packages/angular/test/base/e2e/src/lazy/popover.spec.ts similarity index 100% rename from packages/angular/test/base/e2e/src/popover.spec.ts rename to packages/angular/test/base/e2e/src/lazy/popover.spec.ts diff --git a/packages/angular/test/base/e2e/src/providers.spec.ts b/packages/angular/test/base/e2e/src/lazy/providers.spec.ts similarity index 100% rename from packages/angular/test/base/e2e/src/providers.spec.ts rename to packages/angular/test/base/e2e/src/lazy/providers.spec.ts diff --git a/packages/angular/test/base/e2e/src/router-link.spec.ts b/packages/angular/test/base/e2e/src/lazy/router-link.spec.ts similarity index 100% rename from packages/angular/test/base/e2e/src/router-link.spec.ts rename to packages/angular/test/base/e2e/src/lazy/router-link.spec.ts diff --git a/packages/angular/test/base/e2e/src/routing.spec.ts b/packages/angular/test/base/e2e/src/lazy/routing.spec.ts similarity index 100% rename from packages/angular/test/base/e2e/src/routing.spec.ts rename to packages/angular/test/base/e2e/src/lazy/routing.spec.ts diff --git a/packages/angular/test/base/e2e/src/searchbar.spec.ts b/packages/angular/test/base/e2e/src/lazy/searchbar.spec.ts similarity index 100% rename from packages/angular/test/base/e2e/src/searchbar.spec.ts rename to packages/angular/test/base/e2e/src/lazy/searchbar.spec.ts diff --git a/packages/angular/test/base/e2e/src/standalone-routing.spec.ts b/packages/angular/test/base/e2e/src/lazy/standalone-routing.spec.ts similarity index 100% rename from packages/angular/test/base/e2e/src/standalone-routing.spec.ts rename to packages/angular/test/base/e2e/src/lazy/standalone-routing.spec.ts diff --git a/packages/angular/test/base/e2e/src/tabs.spec.ts b/packages/angular/test/base/e2e/src/lazy/tabs.spec.ts similarity index 100% rename from packages/angular/test/base/e2e/src/tabs.spec.ts rename to packages/angular/test/base/e2e/src/lazy/tabs.spec.ts diff --git a/packages/angular/test/base/e2e/src/textarea.spec.ts b/packages/angular/test/base/e2e/src/lazy/textarea.spec.ts similarity index 100% rename from packages/angular/test/base/e2e/src/textarea.spec.ts rename to packages/angular/test/base/e2e/src/lazy/textarea.spec.ts diff --git a/packages/angular/test/base/e2e/src/view-child.spec.ts b/packages/angular/test/base/e2e/src/lazy/view-child.spec.ts similarity index 100% rename from packages/angular/test/base/e2e/src/view-child.spec.ts rename to packages/angular/test/base/e2e/src/lazy/view-child.spec.ts diff --git a/packages/angular/test/base/src/app/app.routes.ts b/packages/angular/test/base/src/app/app.routes.ts index f32930b100f..679d1812259 100644 --- a/packages/angular/test/base/src/app/app.routes.ts +++ b/packages/angular/test/base/src/app/app.routes.ts @@ -11,6 +11,6 @@ export const routes: Routes = [ */ redirectTo: 'lazy' }, - { path: 'lazy', loadChildren: () => import('./app-lazy/app.module').then(m => m.AppModule) }, - { path: 'standalone', loadChildren: () => import('./app-standalone/app.routes').then(m => m.routes) } + { path: 'lazy', loadChildren: () => import('./lazy/app-lazy/app.module').then(m => m.AppModule) }, + { path: 'standalone', loadChildren: () => import('./standalone/app-standalone/app.routes').then(m => m.routes) } ]; diff --git a/packages/angular/test/base/src/app/accordion/accordion-modal/accordion-modal.component.html b/packages/angular/test/base/src/app/lazy/accordion/accordion-modal/accordion-modal.component.html similarity index 100% rename from packages/angular/test/base/src/app/accordion/accordion-modal/accordion-modal.component.html rename to packages/angular/test/base/src/app/lazy/accordion/accordion-modal/accordion-modal.component.html diff --git a/packages/angular/test/base/src/app/accordion/accordion-modal/accordion-modal.component.ts b/packages/angular/test/base/src/app/lazy/accordion/accordion-modal/accordion-modal.component.ts similarity index 100% rename from packages/angular/test/base/src/app/accordion/accordion-modal/accordion-modal.component.ts rename to packages/angular/test/base/src/app/lazy/accordion/accordion-modal/accordion-modal.component.ts diff --git a/packages/angular/test/base/src/app/accordion/accordion.component.html b/packages/angular/test/base/src/app/lazy/accordion/accordion.component.html similarity index 100% rename from packages/angular/test/base/src/app/accordion/accordion.component.html rename to packages/angular/test/base/src/app/lazy/accordion/accordion.component.html diff --git a/packages/angular/test/base/src/app/accordion/accordion.component.ts b/packages/angular/test/base/src/app/lazy/accordion/accordion.component.ts similarity index 100% rename from packages/angular/test/base/src/app/accordion/accordion.component.ts rename to packages/angular/test/base/src/app/lazy/accordion/accordion.component.ts diff --git a/packages/angular/test/base/src/app/alert/alert.component.html b/packages/angular/test/base/src/app/lazy/alert/alert.component.html similarity index 100% rename from packages/angular/test/base/src/app/alert/alert.component.html rename to packages/angular/test/base/src/app/lazy/alert/alert.component.html diff --git a/packages/angular/test/base/src/app/alert/alert.component.ts b/packages/angular/test/base/src/app/lazy/alert/alert.component.ts similarity index 100% rename from packages/angular/test/base/src/app/alert/alert.component.ts rename to packages/angular/test/base/src/app/lazy/alert/alert.component.ts diff --git a/packages/angular/test/base/src/app/app-lazy/app-routing.module.ts b/packages/angular/test/base/src/app/lazy/app-lazy/app-routing.module.ts similarity index 100% rename from packages/angular/test/base/src/app/app-lazy/app-routing.module.ts rename to packages/angular/test/base/src/app/lazy/app-lazy/app-routing.module.ts diff --git a/packages/angular/test/base/src/app/app-lazy/app.component.html b/packages/angular/test/base/src/app/lazy/app-lazy/app.component.html similarity index 100% rename from packages/angular/test/base/src/app/app-lazy/app.component.html rename to packages/angular/test/base/src/app/lazy/app-lazy/app.component.html diff --git a/packages/angular/test/base/src/app/app-lazy/app.component.ts b/packages/angular/test/base/src/app/lazy/app-lazy/app.component.ts similarity index 100% rename from packages/angular/test/base/src/app/app-lazy/app.component.ts rename to packages/angular/test/base/src/app/lazy/app-lazy/app.component.ts diff --git a/packages/angular/test/base/src/app/app-lazy/app.module.ts b/packages/angular/test/base/src/app/lazy/app-lazy/app.module.ts similarity index 100% rename from packages/angular/test/base/src/app/app-lazy/app.module.ts rename to packages/angular/test/base/src/app/lazy/app-lazy/app.module.ts diff --git a/packages/angular/test/base/src/app/app-lazy/app.routes.ts b/packages/angular/test/base/src/app/lazy/app-lazy/app.routes.ts similarity index 96% rename from packages/angular/test/base/src/app/app-lazy/app.routes.ts rename to packages/angular/test/base/src/app/lazy/app-lazy/app.routes.ts index 7d2c12b9bed..8f96df5270f 100644 --- a/packages/angular/test/base/src/app/app-lazy/app.routes.ts +++ b/packages/angular/test/base/src/app/lazy/app-lazy/app.routes.ts @@ -43,7 +43,7 @@ export const routes: Routes = [ { path: 'router-link-page', component: RouterLinkPageComponent }, { path: 'router-link-page2/:id', component: RouterLinkPage2Component }, { path: 'router-link-page3', component: RouterLinkPage3Component }, - { path: 'standalone', loadComponent: () => import('../standalone/standalone.component').then(c => c.StandaloneComponent) }, + { path: 'standalone', loadComponent: () => import('../routing-standalone/standalone.component').then(c => c.StandaloneComponent) }, { path: 'tabs', redirectTo: '/lazy/tabs/account', pathMatch: 'full' }, { path: 'navigation', diff --git a/packages/angular/test/base/src/app/form-controls/range/range-routing.module.ts b/packages/angular/test/base/src/app/lazy/form-controls/range/range-routing.module.ts similarity index 100% rename from packages/angular/test/base/src/app/form-controls/range/range-routing.module.ts rename to packages/angular/test/base/src/app/lazy/form-controls/range/range-routing.module.ts diff --git a/packages/angular/test/base/src/app/form-controls/range/range.component.html b/packages/angular/test/base/src/app/lazy/form-controls/range/range.component.html similarity index 100% rename from packages/angular/test/base/src/app/form-controls/range/range.component.html rename to packages/angular/test/base/src/app/lazy/form-controls/range/range.component.html diff --git a/packages/angular/test/base/src/app/form-controls/range/range.component.ts b/packages/angular/test/base/src/app/lazy/form-controls/range/range.component.ts similarity index 100% rename from packages/angular/test/base/src/app/form-controls/range/range.component.ts rename to packages/angular/test/base/src/app/lazy/form-controls/range/range.component.ts diff --git a/packages/angular/test/base/src/app/form-controls/range/range.module.ts b/packages/angular/test/base/src/app/lazy/form-controls/range/range.module.ts similarity index 100% rename from packages/angular/test/base/src/app/form-controls/range/range.module.ts rename to packages/angular/test/base/src/app/lazy/form-controls/range/range.module.ts diff --git a/packages/angular/test/base/src/app/form/form.component.html b/packages/angular/test/base/src/app/lazy/form/form.component.html similarity index 100% rename from packages/angular/test/base/src/app/form/form.component.html rename to packages/angular/test/base/src/app/lazy/form/form.component.html diff --git a/packages/angular/test/base/src/app/form/form.component.ts b/packages/angular/test/base/src/app/lazy/form/form.component.ts similarity index 100% rename from packages/angular/test/base/src/app/form/form.component.ts rename to packages/angular/test/base/src/app/lazy/form/form.component.ts diff --git a/packages/angular/test/base/src/app/home-page/home-page.component.html b/packages/angular/test/base/src/app/lazy/home-page/home-page.component.html similarity index 100% rename from packages/angular/test/base/src/app/home-page/home-page.component.html rename to packages/angular/test/base/src/app/lazy/home-page/home-page.component.html diff --git a/packages/angular/test/base/src/app/home-page/home-page.component.ts b/packages/angular/test/base/src/app/lazy/home-page/home-page.component.ts similarity index 100% rename from packages/angular/test/base/src/app/home-page/home-page.component.ts rename to packages/angular/test/base/src/app/lazy/home-page/home-page.component.ts diff --git a/packages/angular/test/base/src/app/inputs/inputs.component.html b/packages/angular/test/base/src/app/lazy/inputs/inputs.component.html similarity index 100% rename from packages/angular/test/base/src/app/inputs/inputs.component.html rename to packages/angular/test/base/src/app/lazy/inputs/inputs.component.html diff --git a/packages/angular/test/base/src/app/inputs/inputs.component.ts b/packages/angular/test/base/src/app/lazy/inputs/inputs.component.ts similarity index 100% rename from packages/angular/test/base/src/app/inputs/inputs.component.ts rename to packages/angular/test/base/src/app/lazy/inputs/inputs.component.ts diff --git a/packages/angular/test/base/src/app/keep-contents-mounted/index.ts b/packages/angular/test/base/src/app/lazy/keep-contents-mounted/index.ts similarity index 100% rename from packages/angular/test/base/src/app/keep-contents-mounted/index.ts rename to packages/angular/test/base/src/app/lazy/keep-contents-mounted/index.ts diff --git a/packages/angular/test/base/src/app/keep-contents-mounted/keep-contents-mounted-routing.module.ts b/packages/angular/test/base/src/app/lazy/keep-contents-mounted/keep-contents-mounted-routing.module.ts similarity index 100% rename from packages/angular/test/base/src/app/keep-contents-mounted/keep-contents-mounted-routing.module.ts rename to packages/angular/test/base/src/app/lazy/keep-contents-mounted/keep-contents-mounted-routing.module.ts diff --git a/packages/angular/test/base/src/app/keep-contents-mounted/keep-contents-mounted.component.html b/packages/angular/test/base/src/app/lazy/keep-contents-mounted/keep-contents-mounted.component.html similarity index 100% rename from packages/angular/test/base/src/app/keep-contents-mounted/keep-contents-mounted.component.html rename to packages/angular/test/base/src/app/lazy/keep-contents-mounted/keep-contents-mounted.component.html diff --git a/packages/angular/test/base/src/app/keep-contents-mounted/keep-contents-mounted.component.ts b/packages/angular/test/base/src/app/lazy/keep-contents-mounted/keep-contents-mounted.component.ts similarity index 100% rename from packages/angular/test/base/src/app/keep-contents-mounted/keep-contents-mounted.component.ts rename to packages/angular/test/base/src/app/lazy/keep-contents-mounted/keep-contents-mounted.component.ts diff --git a/packages/angular/test/base/src/app/keep-contents-mounted/keep-contents-mounted.module.ts b/packages/angular/test/base/src/app/lazy/keep-contents-mounted/keep-contents-mounted.module.ts similarity index 100% rename from packages/angular/test/base/src/app/keep-contents-mounted/keep-contents-mounted.module.ts rename to packages/angular/test/base/src/app/lazy/keep-contents-mounted/keep-contents-mounted.module.ts diff --git a/packages/angular/test/base/src/app/modal-example/modal-example.component.html b/packages/angular/test/base/src/app/lazy/modal-example/modal-example.component.html similarity index 100% rename from packages/angular/test/base/src/app/modal-example/modal-example.component.html rename to packages/angular/test/base/src/app/lazy/modal-example/modal-example.component.html diff --git a/packages/angular/test/base/src/app/modal-example/modal-example.component.ts b/packages/angular/test/base/src/app/lazy/modal-example/modal-example.component.ts similarity index 100% rename from packages/angular/test/base/src/app/modal-example/modal-example.component.ts rename to packages/angular/test/base/src/app/lazy/modal-example/modal-example.component.ts diff --git a/packages/angular/test/base/src/app/modal-inline/index.ts b/packages/angular/test/base/src/app/lazy/modal-inline/index.ts similarity index 100% rename from packages/angular/test/base/src/app/modal-inline/index.ts rename to packages/angular/test/base/src/app/lazy/modal-inline/index.ts diff --git a/packages/angular/test/base/src/app/modal-inline/modal-inline-routing.module.ts b/packages/angular/test/base/src/app/lazy/modal-inline/modal-inline-routing.module.ts similarity index 100% rename from packages/angular/test/base/src/app/modal-inline/modal-inline-routing.module.ts rename to packages/angular/test/base/src/app/lazy/modal-inline/modal-inline-routing.module.ts diff --git a/packages/angular/test/base/src/app/modal-inline/modal-inline.component.html b/packages/angular/test/base/src/app/lazy/modal-inline/modal-inline.component.html similarity index 100% rename from packages/angular/test/base/src/app/modal-inline/modal-inline.component.html rename to packages/angular/test/base/src/app/lazy/modal-inline/modal-inline.component.html diff --git a/packages/angular/test/base/src/app/modal-inline/modal-inline.component.ts b/packages/angular/test/base/src/app/lazy/modal-inline/modal-inline.component.ts similarity index 100% rename from packages/angular/test/base/src/app/modal-inline/modal-inline.component.ts rename to packages/angular/test/base/src/app/lazy/modal-inline/modal-inline.component.ts diff --git a/packages/angular/test/base/src/app/modal-inline/modal-inline.module.ts b/packages/angular/test/base/src/app/lazy/modal-inline/modal-inline.module.ts similarity index 100% rename from packages/angular/test/base/src/app/modal-inline/modal-inline.module.ts rename to packages/angular/test/base/src/app/lazy/modal-inline/modal-inline.module.ts diff --git a/packages/angular/test/base/src/app/modal/modal.component.html b/packages/angular/test/base/src/app/lazy/modal/modal.component.html similarity index 100% rename from packages/angular/test/base/src/app/modal/modal.component.html rename to packages/angular/test/base/src/app/lazy/modal/modal.component.html diff --git a/packages/angular/test/base/src/app/modal/modal.component.ts b/packages/angular/test/base/src/app/lazy/modal/modal.component.ts similarity index 100% rename from packages/angular/test/base/src/app/modal/modal.component.ts rename to packages/angular/test/base/src/app/lazy/modal/modal.component.ts diff --git a/packages/angular/test/base/src/app/nav/nav.component.html b/packages/angular/test/base/src/app/lazy/nav/nav.component.html similarity index 100% rename from packages/angular/test/base/src/app/nav/nav.component.html rename to packages/angular/test/base/src/app/lazy/nav/nav.component.html diff --git a/packages/angular/test/base/src/app/nav/nav.component.ts b/packages/angular/test/base/src/app/lazy/nav/nav.component.ts similarity index 100% rename from packages/angular/test/base/src/app/nav/nav.component.ts rename to packages/angular/test/base/src/app/lazy/nav/nav.component.ts diff --git a/packages/angular/test/base/src/app/navigation-page1/navigation-page1.component.html b/packages/angular/test/base/src/app/lazy/navigation-page1/navigation-page1.component.html similarity index 100% rename from packages/angular/test/base/src/app/navigation-page1/navigation-page1.component.html rename to packages/angular/test/base/src/app/lazy/navigation-page1/navigation-page1.component.html diff --git a/packages/angular/test/base/src/app/navigation-page1/navigation-page1.component.ts b/packages/angular/test/base/src/app/lazy/navigation-page1/navigation-page1.component.ts similarity index 100% rename from packages/angular/test/base/src/app/navigation-page1/navigation-page1.component.ts rename to packages/angular/test/base/src/app/lazy/navigation-page1/navigation-page1.component.ts diff --git a/packages/angular/test/base/src/app/navigation-page2/navigation-page2.component.html b/packages/angular/test/base/src/app/lazy/navigation-page2/navigation-page2.component.html similarity index 100% rename from packages/angular/test/base/src/app/navigation-page2/navigation-page2.component.html rename to packages/angular/test/base/src/app/lazy/navigation-page2/navigation-page2.component.html diff --git a/packages/angular/test/base/src/app/navigation-page2/navigation-page2.component.ts b/packages/angular/test/base/src/app/lazy/navigation-page2/navigation-page2.component.ts similarity index 100% rename from packages/angular/test/base/src/app/navigation-page2/navigation-page2.component.ts rename to packages/angular/test/base/src/app/lazy/navigation-page2/navigation-page2.component.ts diff --git a/packages/angular/test/base/src/app/navigation-page3/navigation-page3.component.html b/packages/angular/test/base/src/app/lazy/navigation-page3/navigation-page3.component.html similarity index 100% rename from packages/angular/test/base/src/app/navigation-page3/navigation-page3.component.html rename to packages/angular/test/base/src/app/lazy/navigation-page3/navigation-page3.component.html diff --git a/packages/angular/test/base/src/app/navigation-page3/navigation-page3.component.ts b/packages/angular/test/base/src/app/lazy/navigation-page3/navigation-page3.component.ts similarity index 100% rename from packages/angular/test/base/src/app/navigation-page3/navigation-page3.component.ts rename to packages/angular/test/base/src/app/lazy/navigation-page3/navigation-page3.component.ts diff --git a/packages/angular/test/base/src/app/nested-outlet-page/nested-outlet-page.component.html b/packages/angular/test/base/src/app/lazy/nested-outlet-page/nested-outlet-page.component.html similarity index 100% rename from packages/angular/test/base/src/app/nested-outlet-page/nested-outlet-page.component.html rename to packages/angular/test/base/src/app/lazy/nested-outlet-page/nested-outlet-page.component.html diff --git a/packages/angular/test/base/src/app/nested-outlet-page/nested-outlet-page.component.ts b/packages/angular/test/base/src/app/lazy/nested-outlet-page/nested-outlet-page.component.ts similarity index 100% rename from packages/angular/test/base/src/app/nested-outlet-page/nested-outlet-page.component.ts rename to packages/angular/test/base/src/app/lazy/nested-outlet-page/nested-outlet-page.component.ts diff --git a/packages/angular/test/base/src/app/nested-outlet-page2/nested-outlet-page2.component.html b/packages/angular/test/base/src/app/lazy/nested-outlet-page2/nested-outlet-page2.component.html similarity index 100% rename from packages/angular/test/base/src/app/nested-outlet-page2/nested-outlet-page2.component.html rename to packages/angular/test/base/src/app/lazy/nested-outlet-page2/nested-outlet-page2.component.html diff --git a/packages/angular/test/base/src/app/nested-outlet-page2/nested-outlet-page2.component.ts b/packages/angular/test/base/src/app/lazy/nested-outlet-page2/nested-outlet-page2.component.ts similarity index 100% rename from packages/angular/test/base/src/app/nested-outlet-page2/nested-outlet-page2.component.ts rename to packages/angular/test/base/src/app/lazy/nested-outlet-page2/nested-outlet-page2.component.ts diff --git a/packages/angular/test/base/src/app/nested-outlet/nested-outlet.component.html b/packages/angular/test/base/src/app/lazy/nested-outlet/nested-outlet.component.html similarity index 100% rename from packages/angular/test/base/src/app/nested-outlet/nested-outlet.component.html rename to packages/angular/test/base/src/app/lazy/nested-outlet/nested-outlet.component.html diff --git a/packages/angular/test/base/src/app/nested-outlet/nested-outlet.component.ts b/packages/angular/test/base/src/app/lazy/nested-outlet/nested-outlet.component.ts similarity index 100% rename from packages/angular/test/base/src/app/nested-outlet/nested-outlet.component.ts rename to packages/angular/test/base/src/app/lazy/nested-outlet/nested-outlet.component.ts diff --git a/packages/angular/test/base/src/app/overlays-inline/index.ts b/packages/angular/test/base/src/app/lazy/overlays-inline/index.ts similarity index 100% rename from packages/angular/test/base/src/app/overlays-inline/index.ts rename to packages/angular/test/base/src/app/lazy/overlays-inline/index.ts diff --git a/packages/angular/test/base/src/app/overlays-inline/overlays-inline-routing.module.ts b/packages/angular/test/base/src/app/lazy/overlays-inline/overlays-inline-routing.module.ts similarity index 100% rename from packages/angular/test/base/src/app/overlays-inline/overlays-inline-routing.module.ts rename to packages/angular/test/base/src/app/lazy/overlays-inline/overlays-inline-routing.module.ts diff --git a/packages/angular/test/base/src/app/overlays-inline/overlays-inline.component.html b/packages/angular/test/base/src/app/lazy/overlays-inline/overlays-inline.component.html similarity index 100% rename from packages/angular/test/base/src/app/overlays-inline/overlays-inline.component.html rename to packages/angular/test/base/src/app/lazy/overlays-inline/overlays-inline.component.html diff --git a/packages/angular/test/base/src/app/overlays-inline/overlays-inline.component.ts b/packages/angular/test/base/src/app/lazy/overlays-inline/overlays-inline.component.ts similarity index 100% rename from packages/angular/test/base/src/app/overlays-inline/overlays-inline.component.ts rename to packages/angular/test/base/src/app/lazy/overlays-inline/overlays-inline.component.ts diff --git a/packages/angular/test/base/src/app/overlays-inline/overlays-inline.module.ts b/packages/angular/test/base/src/app/lazy/overlays-inline/overlays-inline.module.ts similarity index 100% rename from packages/angular/test/base/src/app/overlays-inline/overlays-inline.module.ts rename to packages/angular/test/base/src/app/lazy/overlays-inline/overlays-inline.module.ts diff --git a/packages/angular/test/base/src/app/popover-inline/index.ts b/packages/angular/test/base/src/app/lazy/popover-inline/index.ts similarity index 100% rename from packages/angular/test/base/src/app/popover-inline/index.ts rename to packages/angular/test/base/src/app/lazy/popover-inline/index.ts diff --git a/packages/angular/test/base/src/app/popover-inline/popover-inline-routing.module.ts b/packages/angular/test/base/src/app/lazy/popover-inline/popover-inline-routing.module.ts similarity index 100% rename from packages/angular/test/base/src/app/popover-inline/popover-inline-routing.module.ts rename to packages/angular/test/base/src/app/lazy/popover-inline/popover-inline-routing.module.ts diff --git a/packages/angular/test/base/src/app/popover-inline/popover-inline.component.html b/packages/angular/test/base/src/app/lazy/popover-inline/popover-inline.component.html similarity index 100% rename from packages/angular/test/base/src/app/popover-inline/popover-inline.component.html rename to packages/angular/test/base/src/app/lazy/popover-inline/popover-inline.component.html diff --git a/packages/angular/test/base/src/app/popover-inline/popover-inline.component.ts b/packages/angular/test/base/src/app/lazy/popover-inline/popover-inline.component.ts similarity index 100% rename from packages/angular/test/base/src/app/popover-inline/popover-inline.component.ts rename to packages/angular/test/base/src/app/lazy/popover-inline/popover-inline.component.ts diff --git a/packages/angular/test/base/src/app/popover-inline/popover-inline.module.ts b/packages/angular/test/base/src/app/lazy/popover-inline/popover-inline.module.ts similarity index 100% rename from packages/angular/test/base/src/app/popover-inline/popover-inline.module.ts rename to packages/angular/test/base/src/app/lazy/popover-inline/popover-inline.module.ts diff --git a/packages/angular/test/base/src/app/providers/providers.component.html b/packages/angular/test/base/src/app/lazy/providers/providers.component.html similarity index 100% rename from packages/angular/test/base/src/app/providers/providers.component.html rename to packages/angular/test/base/src/app/lazy/providers/providers.component.html diff --git a/packages/angular/test/base/src/app/providers/providers.component.ts b/packages/angular/test/base/src/app/lazy/providers/providers.component.ts similarity index 100% rename from packages/angular/test/base/src/app/providers/providers.component.ts rename to packages/angular/test/base/src/app/lazy/providers/providers.component.ts diff --git a/packages/angular/test/base/src/app/router-link-page/router-link-page.component.html b/packages/angular/test/base/src/app/lazy/router-link-page/router-link-page.component.html similarity index 100% rename from packages/angular/test/base/src/app/router-link-page/router-link-page.component.html rename to packages/angular/test/base/src/app/lazy/router-link-page/router-link-page.component.html diff --git a/packages/angular/test/base/src/app/router-link-page/router-link-page.component.ts b/packages/angular/test/base/src/app/lazy/router-link-page/router-link-page.component.ts similarity index 100% rename from packages/angular/test/base/src/app/router-link-page/router-link-page.component.ts rename to packages/angular/test/base/src/app/lazy/router-link-page/router-link-page.component.ts diff --git a/packages/angular/test/base/src/app/router-link-page2/router-link-page2.component.html b/packages/angular/test/base/src/app/lazy/router-link-page2/router-link-page2.component.html similarity index 100% rename from packages/angular/test/base/src/app/router-link-page2/router-link-page2.component.html rename to packages/angular/test/base/src/app/lazy/router-link-page2/router-link-page2.component.html diff --git a/packages/angular/test/base/src/app/router-link-page2/router-link-page2.component.ts b/packages/angular/test/base/src/app/lazy/router-link-page2/router-link-page2.component.ts similarity index 100% rename from packages/angular/test/base/src/app/router-link-page2/router-link-page2.component.ts rename to packages/angular/test/base/src/app/lazy/router-link-page2/router-link-page2.component.ts diff --git a/packages/angular/test/base/src/app/router-link-page3/router-link-page3.component.html b/packages/angular/test/base/src/app/lazy/router-link-page3/router-link-page3.component.html similarity index 100% rename from packages/angular/test/base/src/app/router-link-page3/router-link-page3.component.html rename to packages/angular/test/base/src/app/lazy/router-link-page3/router-link-page3.component.html diff --git a/packages/angular/test/base/src/app/router-link-page3/router-link-page3.component.ts b/packages/angular/test/base/src/app/lazy/router-link-page3/router-link-page3.component.ts similarity index 100% rename from packages/angular/test/base/src/app/router-link-page3/router-link-page3.component.ts rename to packages/angular/test/base/src/app/lazy/router-link-page3/router-link-page3.component.ts diff --git a/packages/angular/test/base/src/app/router-link/router-link.component.html b/packages/angular/test/base/src/app/lazy/router-link/router-link.component.html similarity index 100% rename from packages/angular/test/base/src/app/router-link/router-link.component.html rename to packages/angular/test/base/src/app/lazy/router-link/router-link.component.html diff --git a/packages/angular/test/base/src/app/router-link/router-link.component.ts b/packages/angular/test/base/src/app/lazy/router-link/router-link.component.ts similarity index 100% rename from packages/angular/test/base/src/app/router-link/router-link.component.ts rename to packages/angular/test/base/src/app/lazy/router-link/router-link.component.ts diff --git a/packages/angular/test/base/src/app/lazy/routing-standalone/standalone.component.html b/packages/angular/test/base/src/app/lazy/routing-standalone/standalone.component.html new file mode 100644 index 00000000000..aea664b4048 --- /dev/null +++ b/packages/angular/test/base/src/app/lazy/routing-standalone/standalone.component.html @@ -0,0 +1,4 @@ + +

This is a standalone component rendered from a route.

+ Return home +
diff --git a/packages/angular/test/base/src/app/lazy/routing-standalone/standalone.component.ts b/packages/angular/test/base/src/app/lazy/routing-standalone/standalone.component.ts new file mode 100644 index 00000000000..56f6f8a92b0 --- /dev/null +++ b/packages/angular/test/base/src/app/lazy/routing-standalone/standalone.component.ts @@ -0,0 +1,12 @@ +import { Component } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { IonicModule } from '@ionic/angular'; + +@Component({ + selector: 'app-standalone', + templateUrl: './standalone.component.html', + standalone: true, + imports: [IonicModule, RouterModule] +}) +export class StandaloneComponent { } diff --git a/packages/angular/test/base/src/app/searchbar/searchbar-routing.module.ts b/packages/angular/test/base/src/app/lazy/searchbar/searchbar-routing.module.ts similarity index 100% rename from packages/angular/test/base/src/app/searchbar/searchbar-routing.module.ts rename to packages/angular/test/base/src/app/lazy/searchbar/searchbar-routing.module.ts diff --git a/packages/angular/test/base/src/app/searchbar/searchbar.component.html b/packages/angular/test/base/src/app/lazy/searchbar/searchbar.component.html similarity index 100% rename from packages/angular/test/base/src/app/searchbar/searchbar.component.html rename to packages/angular/test/base/src/app/lazy/searchbar/searchbar.component.html diff --git a/packages/angular/test/base/src/app/searchbar/searchbar.component.ts b/packages/angular/test/base/src/app/lazy/searchbar/searchbar.component.ts similarity index 100% rename from packages/angular/test/base/src/app/searchbar/searchbar.component.ts rename to packages/angular/test/base/src/app/lazy/searchbar/searchbar.component.ts diff --git a/packages/angular/test/base/src/app/searchbar/searchbar.module.ts b/packages/angular/test/base/src/app/lazy/searchbar/searchbar.module.ts similarity index 100% rename from packages/angular/test/base/src/app/searchbar/searchbar.module.ts rename to packages/angular/test/base/src/app/lazy/searchbar/searchbar.module.ts diff --git a/packages/angular/test/base/src/app/slides/slides.component.ts b/packages/angular/test/base/src/app/lazy/slides/slides.component.ts similarity index 100% rename from packages/angular/test/base/src/app/slides/slides.component.ts rename to packages/angular/test/base/src/app/lazy/slides/slides.component.ts diff --git a/packages/angular/test/base/src/app/tabs-global/tabs-global-routing.module.ts b/packages/angular/test/base/src/app/lazy/tabs-global/tabs-global-routing.module.ts similarity index 100% rename from packages/angular/test/base/src/app/tabs-global/tabs-global-routing.module.ts rename to packages/angular/test/base/src/app/lazy/tabs-global/tabs-global-routing.module.ts diff --git a/packages/angular/test/base/src/app/tabs-global/tabs-global.component.html b/packages/angular/test/base/src/app/lazy/tabs-global/tabs-global.component.html similarity index 100% rename from packages/angular/test/base/src/app/tabs-global/tabs-global.component.html rename to packages/angular/test/base/src/app/lazy/tabs-global/tabs-global.component.html diff --git a/packages/angular/test/base/src/app/tabs-global/tabs-global.component.ts b/packages/angular/test/base/src/app/lazy/tabs-global/tabs-global.component.ts similarity index 100% rename from packages/angular/test/base/src/app/tabs-global/tabs-global.component.ts rename to packages/angular/test/base/src/app/lazy/tabs-global/tabs-global.component.ts diff --git a/packages/angular/test/base/src/app/tabs-global/tabs-global.module.ts b/packages/angular/test/base/src/app/lazy/tabs-global/tabs-global.module.ts similarity index 100% rename from packages/angular/test/base/src/app/tabs-global/tabs-global.module.ts rename to packages/angular/test/base/src/app/lazy/tabs-global/tabs-global.module.ts diff --git a/packages/angular/test/base/src/app/tabs-lazy/tabs-lazy-routing.module.ts b/packages/angular/test/base/src/app/lazy/tabs-lazy/tabs-lazy-routing.module.ts similarity index 100% rename from packages/angular/test/base/src/app/tabs-lazy/tabs-lazy-routing.module.ts rename to packages/angular/test/base/src/app/lazy/tabs-lazy/tabs-lazy-routing.module.ts diff --git a/packages/angular/test/base/src/app/tabs-lazy/tabs-lazy.module.ts b/packages/angular/test/base/src/app/lazy/tabs-lazy/tabs-lazy.module.ts similarity index 100% rename from packages/angular/test/base/src/app/tabs-lazy/tabs-lazy.module.ts rename to packages/angular/test/base/src/app/lazy/tabs-lazy/tabs-lazy.module.ts diff --git a/packages/angular/test/base/src/app/tabs-lazy/tabs-tab3-nested/tabs-tab3-nested.component.html b/packages/angular/test/base/src/app/lazy/tabs-lazy/tabs-tab3-nested/tabs-tab3-nested.component.html similarity index 100% rename from packages/angular/test/base/src/app/tabs-lazy/tabs-tab3-nested/tabs-tab3-nested.component.html rename to packages/angular/test/base/src/app/lazy/tabs-lazy/tabs-tab3-nested/tabs-tab3-nested.component.html diff --git a/packages/angular/test/base/src/app/tabs-lazy/tabs-tab3-nested/tabs-tab3-nested.component.ts b/packages/angular/test/base/src/app/lazy/tabs-lazy/tabs-tab3-nested/tabs-tab3-nested.component.ts similarity index 100% rename from packages/angular/test/base/src/app/tabs-lazy/tabs-tab3-nested/tabs-tab3-nested.component.ts rename to packages/angular/test/base/src/app/lazy/tabs-lazy/tabs-tab3-nested/tabs-tab3-nested.component.ts diff --git a/packages/angular/test/base/src/app/tabs-lazy/tabs-tab3/tabs-tab3.component.html b/packages/angular/test/base/src/app/lazy/tabs-lazy/tabs-tab3/tabs-tab3.component.html similarity index 100% rename from packages/angular/test/base/src/app/tabs-lazy/tabs-tab3/tabs-tab3.component.html rename to packages/angular/test/base/src/app/lazy/tabs-lazy/tabs-tab3/tabs-tab3.component.html diff --git a/packages/angular/test/base/src/app/tabs-lazy/tabs-tab3/tabs-tab3.component.ts b/packages/angular/test/base/src/app/lazy/tabs-lazy/tabs-tab3/tabs-tab3.component.ts similarity index 100% rename from packages/angular/test/base/src/app/tabs-lazy/tabs-tab3/tabs-tab3.component.ts rename to packages/angular/test/base/src/app/lazy/tabs-lazy/tabs-tab3/tabs-tab3.component.ts diff --git a/packages/angular/test/base/src/app/tabs-slots.component.ts b/packages/angular/test/base/src/app/lazy/tabs-slots.component.ts similarity index 100% rename from packages/angular/test/base/src/app/tabs-slots.component.ts rename to packages/angular/test/base/src/app/lazy/tabs-slots.component.ts diff --git a/packages/angular/test/base/src/app/tabs-tab1-nested/tabs-tab1-nested.component.html b/packages/angular/test/base/src/app/lazy/tabs-tab1-nested/tabs-tab1-nested.component.html similarity index 100% rename from packages/angular/test/base/src/app/tabs-tab1-nested/tabs-tab1-nested.component.html rename to packages/angular/test/base/src/app/lazy/tabs-tab1-nested/tabs-tab1-nested.component.html diff --git a/packages/angular/test/base/src/app/tabs-tab1-nested/tabs-tab1-nested.component.ts b/packages/angular/test/base/src/app/lazy/tabs-tab1-nested/tabs-tab1-nested.component.ts similarity index 100% rename from packages/angular/test/base/src/app/tabs-tab1-nested/tabs-tab1-nested.component.ts rename to packages/angular/test/base/src/app/lazy/tabs-tab1-nested/tabs-tab1-nested.component.ts diff --git a/packages/angular/test/base/src/app/tabs-tab1/tabs-tab1.component.html b/packages/angular/test/base/src/app/lazy/tabs-tab1/tabs-tab1.component.html similarity index 100% rename from packages/angular/test/base/src/app/tabs-tab1/tabs-tab1.component.html rename to packages/angular/test/base/src/app/lazy/tabs-tab1/tabs-tab1.component.html diff --git a/packages/angular/test/base/src/app/tabs-tab1/tabs-tab1.component.ts b/packages/angular/test/base/src/app/lazy/tabs-tab1/tabs-tab1.component.ts similarity index 100% rename from packages/angular/test/base/src/app/tabs-tab1/tabs-tab1.component.ts rename to packages/angular/test/base/src/app/lazy/tabs-tab1/tabs-tab1.component.ts diff --git a/packages/angular/test/base/src/app/tabs-tab2/tabs-tab2.component.html b/packages/angular/test/base/src/app/lazy/tabs-tab2/tabs-tab2.component.html similarity index 100% rename from packages/angular/test/base/src/app/tabs-tab2/tabs-tab2.component.html rename to packages/angular/test/base/src/app/lazy/tabs-tab2/tabs-tab2.component.html diff --git a/packages/angular/test/base/src/app/tabs-tab2/tabs-tab2.component.ts b/packages/angular/test/base/src/app/lazy/tabs-tab2/tabs-tab2.component.ts similarity index 100% rename from packages/angular/test/base/src/app/tabs-tab2/tabs-tab2.component.ts rename to packages/angular/test/base/src/app/lazy/tabs-tab2/tabs-tab2.component.ts diff --git a/packages/angular/test/base/src/app/tabs/tabs.component.css b/packages/angular/test/base/src/app/lazy/tabs/tabs.component.css similarity index 100% rename from packages/angular/test/base/src/app/tabs/tabs.component.css rename to packages/angular/test/base/src/app/lazy/tabs/tabs.component.css diff --git a/packages/angular/test/base/src/app/tabs/tabs.component.html b/packages/angular/test/base/src/app/lazy/tabs/tabs.component.html similarity index 100% rename from packages/angular/test/base/src/app/tabs/tabs.component.html rename to packages/angular/test/base/src/app/lazy/tabs/tabs.component.html diff --git a/packages/angular/test/base/src/app/tabs/tabs.component.ts b/packages/angular/test/base/src/app/lazy/tabs/tabs.component.ts similarity index 100% rename from packages/angular/test/base/src/app/tabs/tabs.component.ts rename to packages/angular/test/base/src/app/lazy/tabs/tabs.component.ts diff --git a/packages/angular/test/base/src/app/tabs/tabs.module.ts b/packages/angular/test/base/src/app/lazy/tabs/tabs.module.ts similarity index 100% rename from packages/angular/test/base/src/app/tabs/tabs.module.ts rename to packages/angular/test/base/src/app/lazy/tabs/tabs.module.ts diff --git a/packages/angular/test/base/src/app/tabs/tabs.router.module.ts b/packages/angular/test/base/src/app/lazy/tabs/tabs.router.module.ts similarity index 100% rename from packages/angular/test/base/src/app/tabs/tabs.router.module.ts rename to packages/angular/test/base/src/app/lazy/tabs/tabs.router.module.ts diff --git a/packages/angular/test/base/src/app/textarea/textarea-routing.module.ts b/packages/angular/test/base/src/app/lazy/textarea/textarea-routing.module.ts similarity index 100% rename from packages/angular/test/base/src/app/textarea/textarea-routing.module.ts rename to packages/angular/test/base/src/app/lazy/textarea/textarea-routing.module.ts diff --git a/packages/angular/test/base/src/app/textarea/textarea.component.html b/packages/angular/test/base/src/app/lazy/textarea/textarea.component.html similarity index 100% rename from packages/angular/test/base/src/app/textarea/textarea.component.html rename to packages/angular/test/base/src/app/lazy/textarea/textarea.component.html diff --git a/packages/angular/test/base/src/app/textarea/textarea.component.ts b/packages/angular/test/base/src/app/lazy/textarea/textarea.component.ts similarity index 100% rename from packages/angular/test/base/src/app/textarea/textarea.component.ts rename to packages/angular/test/base/src/app/lazy/textarea/textarea.component.ts diff --git a/packages/angular/test/base/src/app/textarea/textarea.module.ts b/packages/angular/test/base/src/app/lazy/textarea/textarea.module.ts similarity index 100% rename from packages/angular/test/base/src/app/textarea/textarea.module.ts rename to packages/angular/test/base/src/app/lazy/textarea/textarea.module.ts diff --git a/packages/angular/test/base/src/app/version-test/index.ts b/packages/angular/test/base/src/app/lazy/version-test/index.ts similarity index 100% rename from packages/angular/test/base/src/app/version-test/index.ts rename to packages/angular/test/base/src/app/lazy/version-test/index.ts diff --git a/packages/angular/test/base/src/app/version-test/version-test-routing.module.ts b/packages/angular/test/base/src/app/lazy/version-test/version-test-routing.module.ts similarity index 100% rename from packages/angular/test/base/src/app/version-test/version-test-routing.module.ts rename to packages/angular/test/base/src/app/lazy/version-test/version-test-routing.module.ts diff --git a/packages/angular/test/base/src/app/version-test/version-test.component.html b/packages/angular/test/base/src/app/lazy/version-test/version-test.component.html similarity index 100% rename from packages/angular/test/base/src/app/version-test/version-test.component.html rename to packages/angular/test/base/src/app/lazy/version-test/version-test.component.html diff --git a/packages/angular/test/base/src/app/version-test/version-test.component.ts b/packages/angular/test/base/src/app/lazy/version-test/version-test.component.ts similarity index 100% rename from packages/angular/test/base/src/app/version-test/version-test.component.ts rename to packages/angular/test/base/src/app/lazy/version-test/version-test.component.ts diff --git a/packages/angular/test/base/src/app/version-test/version-test.module.ts b/packages/angular/test/base/src/app/lazy/version-test/version-test.module.ts similarity index 100% rename from packages/angular/test/base/src/app/version-test/version-test.module.ts rename to packages/angular/test/base/src/app/lazy/version-test/version-test.module.ts diff --git a/packages/angular/test/base/src/app/view-child/view-child.component.html b/packages/angular/test/base/src/app/lazy/view-child/view-child.component.html similarity index 100% rename from packages/angular/test/base/src/app/view-child/view-child.component.html rename to packages/angular/test/base/src/app/lazy/view-child/view-child.component.html diff --git a/packages/angular/test/base/src/app/view-child/view-child.component.ts b/packages/angular/test/base/src/app/lazy/view-child/view-child.component.ts similarity index 100% rename from packages/angular/test/base/src/app/view-child/view-child.component.ts rename to packages/angular/test/base/src/app/lazy/view-child/view-child.component.ts diff --git a/packages/angular/test/base/src/app/virtual-scroll-detail/virtual-scroll-detail.component.ts b/packages/angular/test/base/src/app/lazy/virtual-scroll-detail/virtual-scroll-detail.component.ts similarity index 100% rename from packages/angular/test/base/src/app/virtual-scroll-detail/virtual-scroll-detail.component.ts rename to packages/angular/test/base/src/app/lazy/virtual-scroll-detail/virtual-scroll-detail.component.ts diff --git a/packages/angular/test/base/src/app/virtual-scroll-inner/virtual-scroll-inner.component.ts b/packages/angular/test/base/src/app/lazy/virtual-scroll-inner/virtual-scroll-inner.component.ts similarity index 100% rename from packages/angular/test/base/src/app/virtual-scroll-inner/virtual-scroll-inner.component.ts rename to packages/angular/test/base/src/app/lazy/virtual-scroll-inner/virtual-scroll-inner.component.ts diff --git a/packages/angular/test/base/src/app/virtual-scroll/virtual-scroll.component.ts b/packages/angular/test/base/src/app/lazy/virtual-scroll/virtual-scroll.component.ts similarity index 100% rename from packages/angular/test/base/src/app/virtual-scroll/virtual-scroll.component.ts rename to packages/angular/test/base/src/app/lazy/virtual-scroll/virtual-scroll.component.ts diff --git a/packages/angular/test/base/src/app/app-standalone/app.component.html b/packages/angular/test/base/src/app/standalone/app-standalone/app.component.html similarity index 100% rename from packages/angular/test/base/src/app/app-standalone/app.component.html rename to packages/angular/test/base/src/app/standalone/app-standalone/app.component.html diff --git a/packages/angular/test/base/src/app/app-standalone/app.component.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.component.ts similarity index 100% rename from packages/angular/test/base/src/app/app-standalone/app.component.ts rename to packages/angular/test/base/src/app/standalone/app-standalone/app.component.ts diff --git a/packages/angular/test/base/src/app/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts similarity index 100% rename from packages/angular/test/base/src/app/app-standalone/app.routes.ts rename to packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts diff --git a/packages/angular/test/base/src/app/test/test.component.html b/packages/angular/test/base/src/app/standalone/test/test.component.html similarity index 100% rename from packages/angular/test/base/src/app/test/test.component.html rename to packages/angular/test/base/src/app/standalone/test/test.component.html diff --git a/packages/angular/test/base/src/app/test/test.component.ts b/packages/angular/test/base/src/app/standalone/test/test.component.ts similarity index 100% rename from packages/angular/test/base/src/app/test/test.component.ts rename to packages/angular/test/base/src/app/standalone/test/test.component.ts From 5b31439ca0f9404363214735bc83b7f6d277ea8e Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Tue, 1 Aug 2023 15:59:53 -0400 Subject: [PATCH 04/24] refactor(angular): move providers to common library (#27899) --- .../src/directives/navigation/nav-params.ts | 0 packages/angular/common/src/index.ts | 24 ++++++++++++-- .../src/providers/action-sheet-controller.ts | 5 +-- .../src/providers/alert-controller.ts | 5 +-- .../src/providers/angular-delegate.ts | 2 +- .../src/providers/animation-controller.ts | 3 +- .../{ => common}/src/providers/config.ts | 2 +- .../src/providers/dom-controller.ts | 0 .../src/providers/gesture-controller.ts | 3 +- .../src/providers/loading-controller.ts | 5 +-- .../src/providers/menu-controller.ts | 2 +- .../src/providers/modal-controller.ts | 5 +-- .../src/providers/picker-controller.ts | 5 +-- .../{ => common}/src/providers/platform.ts | 3 +- .../src/providers/popover-controller.ts | 5 +-- .../src/providers/toast-controller.ts | 5 +-- .../{ => common}/src/types/interfaces.ts | 5 --- .../{src/util => common/src/utils}/overlay.ts | 0 packages/angular/src/app-initialize.ts | 3 +- .../directives/navigation/ion-back-button.ts | 2 +- .../navigation/ion-router-outlet.ts | 2 +- .../src/directives/navigation/nav-delegate.ts | 2 +- .../directives/navigation/stack-controller.ts | 2 +- packages/angular/src/index.ts | 32 ++++++++++--------- packages/angular/src/ionic-module.ts | 5 +-- .../angular/src/providers/nav-controller.ts | 3 +- 26 files changed, 75 insertions(+), 55 deletions(-) rename packages/angular/{ => common}/src/directives/navigation/nav-params.ts (100%) rename packages/angular/{ => common}/src/providers/action-sheet-controller.ts (57%) rename packages/angular/{ => common}/src/providers/alert-controller.ts (56%) rename packages/angular/{ => common}/src/providers/angular-delegate.ts (99%) rename packages/angular/{ => common}/src/providers/animation-controller.ts (86%) rename packages/angular/{ => common}/src/providers/config.ts (92%) rename packages/angular/{ => common}/src/providers/dom-controller.ts (100%) rename packages/angular/{ => common}/src/providers/gesture-controller.ts (82%) rename packages/angular/{ => common}/src/providers/loading-controller.ts (57%) rename packages/angular/{ => common}/src/providers/menu-controller.ts (98%) rename packages/angular/{ => common}/src/providers/modal-controller.ts (79%) rename packages/angular/{ => common}/src/providers/picker-controller.ts (58%) rename packages/angular/{ => common}/src/providers/platform.ts (98%) rename packages/angular/{ => common}/src/providers/popover-controller.ts (79%) rename packages/angular/{ => common}/src/providers/toast-controller.ts (56%) rename packages/angular/{ => common}/src/types/interfaces.ts (66%) rename packages/angular/{src/util => common/src/utils}/overlay.ts (100%) diff --git a/packages/angular/src/directives/navigation/nav-params.ts b/packages/angular/common/src/directives/navigation/nav-params.ts similarity index 100% rename from packages/angular/src/directives/navigation/nav-params.ts rename to packages/angular/common/src/directives/navigation/nav-params.ts diff --git a/packages/angular/common/src/index.ts b/packages/angular/common/src/index.ts index a5a128b2514..9ed4b009796 100644 --- a/packages/angular/common/src/index.ts +++ b/packages/angular/common/src/index.ts @@ -1,3 +1,21 @@ -// This is required to get ng-packagr to build. -// Remove this when you actually have something to export -export const placeholder = true; +export { ActionSheetController } from './providers/action-sheet-controller'; +export { AlertController } from './providers/alert-controller'; +export { LoadingController } from './providers/loading-controller'; +export { MenuController } from './providers/menu-controller'; +export { ModalController } from './providers/modal-controller'; +export { PickerController } from './providers/picker-controller'; +export { PopoverController } from './providers/popover-controller'; +export { ToastController } from './providers/toast-controller'; + +export { AnimationController } from './providers/animation-controller'; +export { GestureController } from './providers/gesture-controller'; +export { DomController } from './providers/dom-controller'; + +export { Config, ConfigToken } from './providers/config'; +export { Platform } from './providers/platform'; + +export { bindLifecycleEvents, AngularDelegate } from './providers/angular-delegate'; + +export type { IonicWindow } from './types/interfaces'; + +export { NavParams } from './directives/navigation/nav-params'; diff --git a/packages/angular/src/providers/action-sheet-controller.ts b/packages/angular/common/src/providers/action-sheet-controller.ts similarity index 57% rename from packages/angular/src/providers/action-sheet-controller.ts rename to packages/angular/common/src/providers/action-sheet-controller.ts index d2c92bd8e0e..7b42cede950 100644 --- a/packages/angular/src/providers/action-sheet-controller.ts +++ b/packages/angular/common/src/providers/action-sheet-controller.ts @@ -1,7 +1,8 @@ import { Injectable } from '@angular/core'; -import { ActionSheetOptions, actionSheetController } from '@ionic/core'; +import type { ActionSheetOptions } from '@ionic/core/components'; +import { actionSheetController } from '@ionic/core/components'; -import { OverlayBaseController } from '../util/overlay'; +import { OverlayBaseController } from '../utils/overlay'; @Injectable({ providedIn: 'root', diff --git a/packages/angular/src/providers/alert-controller.ts b/packages/angular/common/src/providers/alert-controller.ts similarity index 56% rename from packages/angular/src/providers/alert-controller.ts rename to packages/angular/common/src/providers/alert-controller.ts index abffc7577cb..799da17a92a 100644 --- a/packages/angular/src/providers/alert-controller.ts +++ b/packages/angular/common/src/providers/alert-controller.ts @@ -1,7 +1,8 @@ import { Injectable } from '@angular/core'; -import { AlertOptions, alertController } from '@ionic/core'; +import type { AlertOptions } from '@ionic/core/components'; +import { alertController } from '@ionic/core/components'; -import { OverlayBaseController } from '../util/overlay'; +import { OverlayBaseController } from '../utils/overlay'; @Injectable({ providedIn: 'root', diff --git a/packages/angular/src/providers/angular-delegate.ts b/packages/angular/common/src/providers/angular-delegate.ts similarity index 99% rename from packages/angular/src/providers/angular-delegate.ts rename to packages/angular/common/src/providers/angular-delegate.ts index 603a646af87..c1524e8b72c 100644 --- a/packages/angular/src/providers/angular-delegate.ts +++ b/packages/angular/common/src/providers/angular-delegate.ts @@ -16,7 +16,7 @@ import { LIFECYCLE_WILL_ENTER, LIFECYCLE_WILL_LEAVE, LIFECYCLE_WILL_UNLOAD, -} from '@ionic/core'; +} from '@ionic/core/components'; import { NavParams } from '../directives/navigation/nav-params'; diff --git a/packages/angular/src/providers/animation-controller.ts b/packages/angular/common/src/providers/animation-controller.ts similarity index 86% rename from packages/angular/src/providers/animation-controller.ts rename to packages/angular/common/src/providers/animation-controller.ts index abd620c5712..647613ef53a 100644 --- a/packages/angular/src/providers/animation-controller.ts +++ b/packages/angular/common/src/providers/animation-controller.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; -import { Animation, createAnimation, getTimeGivenProgression } from '@ionic/core'; +import type { Animation } from '@ionic/core/components'; +import { createAnimation, getTimeGivenProgression } from '@ionic/core/components'; @Injectable({ providedIn: 'root', diff --git a/packages/angular/src/providers/config.ts b/packages/angular/common/src/providers/config.ts similarity index 92% rename from packages/angular/src/providers/config.ts rename to packages/angular/common/src/providers/config.ts index a312a827093..2c5966fc06f 100644 --- a/packages/angular/src/providers/config.ts +++ b/packages/angular/common/src/providers/config.ts @@ -1,5 +1,5 @@ import { Injectable, InjectionToken } from '@angular/core'; -import { Config as CoreConfig, IonicConfig } from '@ionic/core'; +import type { Config as CoreConfig, IonicConfig } from '@ionic/core/components'; import { IonicWindow } from '../types/interfaces'; diff --git a/packages/angular/src/providers/dom-controller.ts b/packages/angular/common/src/providers/dom-controller.ts similarity index 100% rename from packages/angular/src/providers/dom-controller.ts rename to packages/angular/common/src/providers/dom-controller.ts diff --git a/packages/angular/src/providers/gesture-controller.ts b/packages/angular/common/src/providers/gesture-controller.ts similarity index 82% rename from packages/angular/src/providers/gesture-controller.ts rename to packages/angular/common/src/providers/gesture-controller.ts index 6450c7bff57..6a1564d6553 100644 --- a/packages/angular/src/providers/gesture-controller.ts +++ b/packages/angular/common/src/providers/gesture-controller.ts @@ -1,5 +1,6 @@ import { NgZone, Injectable } from '@angular/core'; -import { Gesture, GestureConfig, createGesture } from '@ionic/core'; +import type { Gesture, GestureConfig } from '@ionic/core/components'; +import { createGesture } from '@ionic/core/components'; @Injectable({ providedIn: 'root', diff --git a/packages/angular/src/providers/loading-controller.ts b/packages/angular/common/src/providers/loading-controller.ts similarity index 57% rename from packages/angular/src/providers/loading-controller.ts rename to packages/angular/common/src/providers/loading-controller.ts index a132949ccc1..d34e09465b0 100644 --- a/packages/angular/src/providers/loading-controller.ts +++ b/packages/angular/common/src/providers/loading-controller.ts @@ -1,7 +1,8 @@ import { Injectable } from '@angular/core'; -import { LoadingOptions, loadingController } from '@ionic/core'; +import type { LoadingOptions } from '@ionic/core/components'; +import { loadingController } from '@ionic/core/components'; -import { OverlayBaseController } from '../util/overlay'; +import { OverlayBaseController } from '../utils/overlay'; @Injectable({ providedIn: 'root', diff --git a/packages/angular/src/providers/menu-controller.ts b/packages/angular/common/src/providers/menu-controller.ts similarity index 98% rename from packages/angular/src/providers/menu-controller.ts rename to packages/angular/common/src/providers/menu-controller.ts index 9898100613e..ba1876e23b8 100644 --- a/packages/angular/src/providers/menu-controller.ts +++ b/packages/angular/common/src/providers/menu-controller.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { menuController } from '@ionic/core'; +import { menuController } from '@ionic/core/components'; @Injectable({ providedIn: 'root', diff --git a/packages/angular/src/providers/modal-controller.ts b/packages/angular/common/src/providers/modal-controller.ts similarity index 79% rename from packages/angular/src/providers/modal-controller.ts rename to packages/angular/common/src/providers/modal-controller.ts index 77053d99b8a..49a69e804a7 100644 --- a/packages/angular/src/providers/modal-controller.ts +++ b/packages/angular/common/src/providers/modal-controller.ts @@ -1,7 +1,8 @@ import { Injector, Injectable, EnvironmentInjector, inject } from '@angular/core'; -import { ModalOptions, modalController } from '@ionic/core'; +import type { ModalOptions } from '@ionic/core/components'; +import { modalController } from '@ionic/core/components'; -import { OverlayBaseController } from '../util/overlay'; +import { OverlayBaseController } from '../utils/overlay'; import { AngularDelegate } from './angular-delegate'; diff --git a/packages/angular/src/providers/picker-controller.ts b/packages/angular/common/src/providers/picker-controller.ts similarity index 58% rename from packages/angular/src/providers/picker-controller.ts rename to packages/angular/common/src/providers/picker-controller.ts index a67fb3f7dbf..2baef2f4d48 100644 --- a/packages/angular/src/providers/picker-controller.ts +++ b/packages/angular/common/src/providers/picker-controller.ts @@ -1,7 +1,8 @@ import { Injectable } from '@angular/core'; -import { PickerOptions, pickerController } from '@ionic/core'; +import type { PickerOptions } from '@ionic/core'; +import { pickerController } from '@ionic/core/components'; -import { OverlayBaseController } from '../util/overlay'; +import { OverlayBaseController } from '../utils/overlay'; @Injectable({ providedIn: 'root', diff --git a/packages/angular/src/providers/platform.ts b/packages/angular/common/src/providers/platform.ts similarity index 98% rename from packages/angular/src/providers/platform.ts rename to packages/angular/common/src/providers/platform.ts index 727aca26819..c8e27c1e268 100644 --- a/packages/angular/src/providers/platform.ts +++ b/packages/angular/common/src/providers/platform.ts @@ -1,6 +1,7 @@ import { DOCUMENT } from '@angular/common'; import { NgZone, Inject, Injectable } from '@angular/core'; -import { BackButtonEventDetail, KeyboardEventDetail, Platforms, getPlatforms, isPlatform } from '@ionic/core'; +import { getPlatforms, isPlatform } from '@ionic/core/components'; +import type { BackButtonEventDetail, KeyboardEventDetail, Platforms } from '@ionic/core/components'; import { Subscription, Subject } from 'rxjs'; // TODO(FW-2827): types diff --git a/packages/angular/src/providers/popover-controller.ts b/packages/angular/common/src/providers/popover-controller.ts similarity index 79% rename from packages/angular/src/providers/popover-controller.ts rename to packages/angular/common/src/providers/popover-controller.ts index 700233ee357..c4c7ccc7251 100644 --- a/packages/angular/src/providers/popover-controller.ts +++ b/packages/angular/common/src/providers/popover-controller.ts @@ -1,7 +1,8 @@ import { Injector, Injectable, inject, EnvironmentInjector } from '@angular/core'; -import { PopoverOptions, popoverController } from '@ionic/core'; +import type { PopoverOptions } from '@ionic/core/components'; +import { popoverController } from '@ionic/core/components'; -import { OverlayBaseController } from '../util/overlay'; +import { OverlayBaseController } from '../utils/overlay'; import { AngularDelegate } from './angular-delegate'; diff --git a/packages/angular/src/providers/toast-controller.ts b/packages/angular/common/src/providers/toast-controller.ts similarity index 56% rename from packages/angular/src/providers/toast-controller.ts rename to packages/angular/common/src/providers/toast-controller.ts index d967b77b9f5..297ee3a2b7e 100644 --- a/packages/angular/src/providers/toast-controller.ts +++ b/packages/angular/common/src/providers/toast-controller.ts @@ -1,7 +1,8 @@ import { Injectable } from '@angular/core'; -import { ToastOptions, toastController } from '@ionic/core'; +import type { ToastOptions } from '@ionic/core/components'; +import { toastController } from '@ionic/core/components'; -import { OverlayBaseController } from '../util/overlay'; +import { OverlayBaseController } from '../utils/overlay'; @Injectable({ providedIn: 'root', diff --git a/packages/angular/src/types/interfaces.ts b/packages/angular/common/src/types/interfaces.ts similarity index 66% rename from packages/angular/src/types/interfaces.ts rename to packages/angular/common/src/types/interfaces.ts index b86fa899f3c..3595fdb03ce 100644 --- a/packages/angular/src/types/interfaces.ts +++ b/packages/angular/common/src/types/interfaces.ts @@ -7,8 +7,3 @@ export interface IonicWindow extends Window { Ionic: IonicGlobal; __zone_symbol__requestAnimationFrame?: (ts: FrameRequestCallback) => number; } - -export interface HTMLStencilElement extends HTMLElement { - componentOnReady?(): Promise; - forceUpdate?(): void; -} diff --git a/packages/angular/src/util/overlay.ts b/packages/angular/common/src/utils/overlay.ts similarity index 100% rename from packages/angular/src/util/overlay.ts rename to packages/angular/common/src/utils/overlay.ts diff --git a/packages/angular/src/app-initialize.ts b/packages/angular/src/app-initialize.ts index 0838a6d9b4d..557777eaf6a 100644 --- a/packages/angular/src/app-initialize.ts +++ b/packages/angular/src/app-initialize.ts @@ -1,9 +1,8 @@ import { NgZone } from '@angular/core'; +import type { Config, IonicWindow } from '@ionic/angular/common'; import { setupConfig } from '@ionic/core'; import { applyPolyfills, defineCustomElements } from '@ionic/core/loader'; -import { Config } from './providers/config'; -import { IonicWindow } from './types/interfaces'; import { raf } from './util/util'; // TODO(FW-2827): types diff --git a/packages/angular/src/directives/navigation/ion-back-button.ts b/packages/angular/src/directives/navigation/ion-back-button.ts index 7dad73bb9d5..210096f598c 100644 --- a/packages/angular/src/directives/navigation/ion-back-button.ts +++ b/packages/angular/src/directives/navigation/ion-back-button.ts @@ -1,7 +1,7 @@ import { Directive, HostListener, Input, Optional } from '@angular/core'; +import { Config } from '@ionic/angular/common'; import { AnimationBuilder } from '@ionic/core'; -import { Config } from '../../providers/config'; import { NavController } from '../../providers/nav-controller'; import { IonRouterOutlet } from './ion-router-outlet'; diff --git a/packages/angular/src/directives/navigation/ion-router-outlet.ts b/packages/angular/src/directives/navigation/ion-router-outlet.ts index c54eaa17f3e..cef96a1c67b 100644 --- a/packages/angular/src/directives/navigation/ion-router-outlet.ts +++ b/packages/angular/src/directives/navigation/ion-router-outlet.ts @@ -21,12 +21,12 @@ import { reflectComponentType, } from '@angular/core'; import { OutletContext, Router, ActivatedRoute, ChildrenOutletContexts, PRIMARY_OUTLET, Data } from '@angular/router'; +import { Config } from '@ionic/angular/common'; import { componentOnReady } from '@ionic/core'; import { Observable, BehaviorSubject, Subscription, combineLatest, of } from 'rxjs'; import { distinctUntilChanged, filter, switchMap } from 'rxjs/operators'; import { AnimationBuilder } from '../../ionic-core'; -import { Config } from '../../providers/config'; import { NavController } from '../../providers/nav-controller'; import { StackController } from './stack-controller'; diff --git a/packages/angular/src/directives/navigation/nav-delegate.ts b/packages/angular/src/directives/navigation/nav-delegate.ts index 051701674fa..bdf7613b85e 100644 --- a/packages/angular/src/directives/navigation/nav-delegate.ts +++ b/packages/angular/src/directives/navigation/nav-delegate.ts @@ -1,6 +1,6 @@ import { ElementRef, Injector, Directive, EnvironmentInjector } from '@angular/core'; +import { AngularDelegate } from '@ionic/angular/common'; -import { AngularDelegate } from '../../providers/angular-delegate'; import { ProxyCmp, proxyOutputs } from '../angular-component-lib/utils'; @ProxyCmp({ diff --git a/packages/angular/src/directives/navigation/stack-controller.ts b/packages/angular/src/directives/navigation/stack-controller.ts index 93520c0a667..809b67315f4 100644 --- a/packages/angular/src/directives/navigation/stack-controller.ts +++ b/packages/angular/src/directives/navigation/stack-controller.ts @@ -1,9 +1,9 @@ import { Location } from '@angular/common'; import { ComponentRef, NgZone } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; +import { bindLifecycleEvents } from '@ionic/angular/common'; import { AnimationBuilder, RouterDirection } from '@ionic/core'; -import { bindLifecycleEvents } from '../../providers/angular-delegate'; import { NavController } from '../../providers/nav-controller'; import { diff --git a/packages/angular/src/index.ts b/packages/angular/src/index.ts index b92167e481a..77860a8daa2 100644 --- a/packages/angular/src/index.ts +++ b/packages/angular/src/index.ts @@ -13,27 +13,29 @@ export { RouterLinkWithHrefDelegateDirective as RouterLinkWithHrefDelegate, } from './directives/navigation/router-link-delegate'; -export { NavParams } from './directives/navigation/nav-params'; export { IonModal } from './directives/overlays/modal'; export { IonPopover } from './directives/overlays/popover'; export * from './directives/proxies'; // PROVIDERS -export { AngularDelegate } from './providers/angular-delegate'; -export { ActionSheetController } from './providers/action-sheet-controller'; -export { AlertController } from './providers/alert-controller'; -export { LoadingController } from './providers/loading-controller'; -export { MenuController } from './providers/menu-controller'; -export { PickerController } from './providers/picker-controller'; -export { ModalController } from './providers/modal-controller'; -export { Platform } from './providers/platform'; -export { PopoverController } from './providers/popover-controller'; -export { ToastController } from './providers/toast-controller'; +export { + ActionSheetController, + AlertController, + LoadingController, + MenuController, + ModalController, + PickerController, + PopoverController, + ToastController, + AnimationController, + GestureController, + DomController, + Config, + Platform, + AngularDelegate, + NavParams, +} from '@ionic/angular/common'; export { NavController } from './providers/nav-controller'; -export { DomController } from './providers/dom-controller'; -export { Config } from './providers/config'; -export { AnimationController } from './providers/animation-controller'; -export { GestureController } from './providers/gesture-controller'; // ROUTER STRATEGY export { IonicRouteStrategy } from './util/ionic-router-reuse-strategy'; diff --git a/packages/angular/src/ionic-module.ts b/packages/angular/src/ionic-module.ts index 32ad4eaa4de..b30fb8b97d0 100644 --- a/packages/angular/src/ionic-module.ts +++ b/packages/angular/src/ionic-module.ts @@ -1,6 +1,7 @@ import { CommonModule, DOCUMENT } from '@angular/common'; import { ModuleWithProviders, APP_INITIALIZER, NgModule, NgZone } from '@angular/core'; import { Router } from '@angular/router'; +import { ModalController, PopoverController, ConfigToken, AngularDelegate } from '@ionic/angular/common'; import { IonicConfig } from '@ionic/core'; import { appInitialize } from './app-initialize'; @@ -22,10 +23,6 @@ import { import { IonModal } from './directives/overlays/modal'; import { IonPopover } from './directives/overlays/popover'; import { DIRECTIVES } from './directives/proxies-list'; -import { AngularDelegate } from './providers/angular-delegate'; -import { ConfigToken } from './providers/config'; -import { ModalController } from './providers/modal-controller'; -import { PopoverController } from './providers/popover-controller'; const DECLARATIONS = [ // generated proxies diff --git a/packages/angular/src/providers/nav-controller.ts b/packages/angular/src/providers/nav-controller.ts index 101967f0f2f..b5859d52496 100644 --- a/packages/angular/src/providers/nav-controller.ts +++ b/packages/angular/src/providers/nav-controller.ts @@ -1,12 +1,11 @@ import { Location } from '@angular/common'; import { Injectable, Optional } from '@angular/core'; import { NavigationExtras, Router, UrlSerializer, UrlTree, NavigationStart } from '@angular/router'; +import { Platform } from '@ionic/angular/common'; import { AnimationBuilder, NavDirection, RouterDirection } from '@ionic/core'; import { IonRouterOutlet } from '../directives/navigation/ion-router-outlet'; -import { Platform } from './platform'; - export interface AnimationOptions { animated?: boolean; animation?: AnimationBuilder; From de48493a16f244cf889a16b6efc589d43363f873 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Fri, 4 Aug 2023 13:46:45 -0400 Subject: [PATCH 05/24] refactor(angular): move remaining directives to common module (#27909) --- .../src/directives/navigation/back-button.ts | 42 ++ .../src/directives/navigation/nav-delegate.ts | 40 ++ .../navigation/router-link-delegate.ts | 106 ++++ .../directives/navigation/router-outlet.ts | 509 ++++++++++++++++++ .../directives/navigation/stack-controller.ts | 4 +- .../src/directives/navigation/stack-utils.ts | 2 +- .../common/src/directives/navigation/tabs.ts | 177 ++++++ packages/angular/common/src/index.ts | 11 + .../src/providers/nav-controller.ts | 7 +- packages/angular/common/src/utils/proxy.ts | 53 ++ .../directives/navigation/ion-back-button.ts | 39 +- .../navigation/ion-router-outlet.ts | 507 +---------------- .../src/directives/navigation/ion-tabs.ts | 167 +----- .../src/directives/navigation/nav-delegate.ts | 38 +- .../navigation/router-link-delegate.ts | 99 +--- packages/angular/src/index.ts | 2 +- packages/angular/src/ionic-module.ts | 11 +- 17 files changed, 977 insertions(+), 837 deletions(-) create mode 100644 packages/angular/common/src/directives/navigation/back-button.ts create mode 100644 packages/angular/common/src/directives/navigation/nav-delegate.ts create mode 100644 packages/angular/common/src/directives/navigation/router-link-delegate.ts create mode 100644 packages/angular/common/src/directives/navigation/router-outlet.ts rename packages/angular/{ => common}/src/directives/navigation/stack-controller.ts (98%) rename packages/angular/{ => common}/src/directives/navigation/stack-utils.ts (96%) create mode 100644 packages/angular/common/src/directives/navigation/tabs.ts rename packages/angular/{ => common}/src/providers/nav-controller.ts (97%) create mode 100644 packages/angular/common/src/utils/proxy.ts diff --git a/packages/angular/common/src/directives/navigation/back-button.ts b/packages/angular/common/src/directives/navigation/back-button.ts new file mode 100644 index 00000000000..8ddc9e14833 --- /dev/null +++ b/packages/angular/common/src/directives/navigation/back-button.ts @@ -0,0 +1,42 @@ +import { Directive, HostListener, Input, Optional } from '@angular/core'; +import type { AnimationBuilder } from '@ionic/core/components'; + +import { Config } from '../../providers/config'; +import { NavController } from '../../providers/nav-controller'; + +import { IonRouterOutlet } from './router-outlet'; + +@Directive({ + selector: 'ion-back-button', +}) +// eslint-disable-next-line @angular-eslint/directive-class-suffix +export class IonBackButton { + @Input() + defaultHref: string | undefined | null; + + @Input() + routerAnimation?: AnimationBuilder; + + constructor( + @Optional() private routerOutlet: IonRouterOutlet, + private navCtrl: NavController, + private config: Config + ) {} + + /** + * @internal + */ + @HostListener('click', ['$event']) + onClick(ev: Event): void { + const defaultHref = this.defaultHref || this.config.get('backButtonDefaultHref'); + + if (this.routerOutlet?.canGoBack()) { + this.navCtrl.setDirection('back', undefined, undefined, this.routerAnimation); + this.routerOutlet.pop(); + ev.preventDefault(); + } else if (defaultHref != null) { + this.navCtrl.navigateBack(defaultHref, { animation: this.routerAnimation }); + ev.preventDefault(); + } + } +} diff --git a/packages/angular/common/src/directives/navigation/nav-delegate.ts b/packages/angular/common/src/directives/navigation/nav-delegate.ts new file mode 100644 index 00000000000..a9dfdd29042 --- /dev/null +++ b/packages/angular/common/src/directives/navigation/nav-delegate.ts @@ -0,0 +1,40 @@ +import { ElementRef, Injector, Directive, EnvironmentInjector } from '@angular/core'; + +import { AngularDelegate } from '../../providers/angular-delegate'; +import { ProxyCmp, proxyOutputs } from '../../utils/proxy'; + +@ProxyCmp({ + inputs: ['animated', 'animation', 'root', 'rootParams', 'swipeGesture'], + methods: [ + 'push', + 'insert', + 'insertPages', + 'pop', + 'popTo', + 'popToRoot', + 'removeIndex', + 'setRoot', + 'setPages', + 'getActive', + 'getByIndex', + 'canGoBack', + 'getPrevious', + ], +}) +@Directive({ + selector: 'ion-nav', +}) +// eslint-disable-next-line @angular-eslint/directive-class-suffix +export class NavDelegate { + protected el: HTMLElement; + constructor( + ref: ElementRef, + environmentInjector: EnvironmentInjector, + injector: Injector, + angularDelegate: AngularDelegate + ) { + this.el = ref.nativeElement; + ref.nativeElement.delegate = angularDelegate.create(environmentInjector, injector); + proxyOutputs(this, this.el, ['ionNavDidChange', 'ionNavWillChange']); + } +} diff --git a/packages/angular/common/src/directives/navigation/router-link-delegate.ts b/packages/angular/common/src/directives/navigation/router-link-delegate.ts new file mode 100644 index 00000000000..d996d739428 --- /dev/null +++ b/packages/angular/common/src/directives/navigation/router-link-delegate.ts @@ -0,0 +1,106 @@ +import { LocationStrategy } from '@angular/common'; +import { ElementRef, OnChanges, OnInit, Directive, HostListener, Input, Optional } from '@angular/core'; +import { Router, RouterLink } from '@angular/router'; +import type { AnimationBuilder, RouterDirection } from '@ionic/core/components'; + +import { NavController } from '../../providers/nav-controller'; + +/** + * Adds support for Ionic routing directions and animations to the base Angular router link directive. + * + * When the router link is clicked, the directive will assign the direction and + * animation so that the routing integration will transition correctly. + */ +@Directive({ + selector: ':not(a):not(area)[routerLink]', +}) +export class RouterLinkDelegateDirective implements OnInit, OnChanges { + @Input() + routerDirection: RouterDirection = 'forward'; + + @Input() + routerAnimation?: AnimationBuilder; + + constructor( + private locationStrategy: LocationStrategy, + private navCtrl: NavController, + private elementRef: ElementRef, + private router: Router, + @Optional() private routerLink?: RouterLink + ) {} + + ngOnInit(): void { + this.updateTargetUrlAndHref(); + } + + ngOnChanges(): void { + this.updateTargetUrlAndHref(); + } + + private updateTargetUrlAndHref() { + if (this.routerLink?.urlTree) { + const href = this.locationStrategy.prepareExternalUrl(this.router.serializeUrl(this.routerLink.urlTree)); + this.elementRef.nativeElement.href = href; + } + } + + /** + * @internal + */ + @HostListener('click', ['$event']) + onClick(ev: UIEvent): void { + this.navCtrl.setDirection(this.routerDirection, undefined, undefined, this.routerAnimation); + + /** + * This prevents the browser from + * performing a page reload when pressing + * an Ionic component with routerLink. + * The page reload interferes with routing + * and causes ion-back-button to disappear + * since the local history is wiped on reload. + */ + ev.preventDefault(); + } +} + +@Directive({ + selector: 'a[routerLink],area[routerLink]', +}) +export class RouterLinkWithHrefDelegateDirective implements OnInit, OnChanges { + @Input() + routerDirection: RouterDirection = 'forward'; + + @Input() + routerAnimation?: AnimationBuilder; + + constructor( + private locationStrategy: LocationStrategy, + private navCtrl: NavController, + private elementRef: ElementRef, + private router: Router, + @Optional() private routerLink?: RouterLink + ) {} + + ngOnInit(): void { + this.updateTargetUrlAndHref(); + } + + ngOnChanges(): void { + this.updateTargetUrlAndHref(); + } + + private updateTargetUrlAndHref() { + if (this.routerLink?.urlTree) { + const href = this.locationStrategy.prepareExternalUrl(this.router.serializeUrl(this.routerLink.urlTree)); + this.elementRef.nativeElement.href = href; + } + } + + /** + * @internal + */ + @HostListener('click') + onClick(): void { + this.navCtrl.setDirection(this.routerDirection, undefined, undefined, this.routerAnimation); + } +} diff --git a/packages/angular/common/src/directives/navigation/router-outlet.ts b/packages/angular/common/src/directives/navigation/router-outlet.ts new file mode 100644 index 00000000000..e57a3faf854 --- /dev/null +++ b/packages/angular/common/src/directives/navigation/router-outlet.ts @@ -0,0 +1,509 @@ +import { Location } from '@angular/common'; +import { + ComponentRef, + ElementRef, + Injector, + NgZone, + OnDestroy, + OnInit, + ViewContainerRef, + inject, + Attribute, + Directive, + EventEmitter, + Optional, + Output, + SkipSelf, + EnvironmentInjector, + Input, + InjectionToken, + Injectable, + reflectComponentType, +} from '@angular/core'; +import { OutletContext, Router, ActivatedRoute, ChildrenOutletContexts, PRIMARY_OUTLET, Data } from '@angular/router'; +import { componentOnReady } from '@ionic/core/components'; +import type { AnimationBuilder } from '@ionic/core/components'; +import { Observable, BehaviorSubject, Subscription, combineLatest, of } from 'rxjs'; +import { distinctUntilChanged, filter, switchMap } from 'rxjs/operators'; + +import { Config } from '../../providers/config'; +import { NavController } from '../../providers/nav-controller'; + +import { StackController } from './stack-controller'; +import { RouteView, getUrl } from './stack-utils'; + +// TODO(FW-2827): types + +@Directive({ + selector: 'ion-router-outlet', + exportAs: 'outlet', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['animated', 'animation', 'mode', 'swipeGesture'], +}) +// eslint-disable-next-line @angular-eslint/directive-class-suffix +export class IonRouterOutlet implements OnDestroy, OnInit { + nativeEl: HTMLIonRouterOutletElement; + activatedView: RouteView | null = null; + tabsPrefix: string | undefined; + + private _swipeGesture?: boolean; + private stackCtrl: StackController; + + // Maintain map of activated route proxies for each component instance + private proxyMap = new WeakMap(); + // Keep the latest activated route in a subject for the proxy routes to switch map to + private currentActivatedRoute$ = new BehaviorSubject<{ component: any; activatedRoute: ActivatedRoute } | null>(null); + + private activated: ComponentRef | null = null; + /** @internal */ + get activatedComponentRef(): ComponentRef | null { + return this.activated; + } + private _activatedRoute: ActivatedRoute | null = null; + + /** + * The name of the outlet + */ + @Input() name = PRIMARY_OUTLET; + + @Output() stackEvents = new EventEmitter(); + // eslint-disable-next-line @angular-eslint/no-output-rename + @Output('activate') activateEvents = new EventEmitter(); + // eslint-disable-next-line @angular-eslint/no-output-rename + @Output('deactivate') deactivateEvents = new EventEmitter(); + + private parentContexts = inject(ChildrenOutletContexts); + private location = inject(ViewContainerRef); + private environmentInjector = inject(EnvironmentInjector); + private inputBinder = inject(INPUT_BINDER, { optional: true }); + /** @nodoc */ + readonly supportsBindingToComponentInputs = true; + + // Ionic providers + private config = inject(Config); + private navCtrl = inject(NavController); + + set animation(animation: AnimationBuilder) { + this.nativeEl.animation = animation; + } + + set animated(animated: boolean) { + this.nativeEl.animated = animated; + } + + set swipeGesture(swipe: boolean) { + this._swipeGesture = swipe; + + this.nativeEl.swipeHandler = swipe + ? { + canStart: () => this.stackCtrl.canGoBack(1) && !this.stackCtrl.hasRunningTask(), + onStart: () => this.stackCtrl.startBackTransition(), + onEnd: (shouldContinue) => this.stackCtrl.endBackTransition(shouldContinue), + } + : undefined; + } + + constructor( + @Attribute('name') name: string, + @Optional() @Attribute('tabs') tabs: string, + commonLocation: Location, + elementRef: ElementRef, + router: Router, + zone: NgZone, + activatedRoute: ActivatedRoute, + @SkipSelf() @Optional() readonly parentOutlet?: IonRouterOutlet + ) { + this.nativeEl = elementRef.nativeElement; + this.name = name || PRIMARY_OUTLET; + this.tabsPrefix = tabs === 'true' ? getUrl(router, activatedRoute) : undefined; + this.stackCtrl = new StackController(this.tabsPrefix, this.nativeEl, router, this.navCtrl, zone, commonLocation); + this.parentContexts.onChildOutletCreated(this.name, this as any); + } + + ngOnDestroy(): void { + this.stackCtrl.destroy(); + this.inputBinder?.unsubscribeFromRouteData(this); + } + + getContext(): OutletContext | null { + return this.parentContexts.getContext(this.name); + } + + ngOnInit(): void { + this.initializeOutletWithName(); + } + + // Note: Ionic deviates from the Angular Router implementation here + private initializeOutletWithName() { + if (!this.activated) { + // If the outlet was not instantiated at the time the route got activated we need to populate + // the outlet when it is initialized (ie inside a NgIf) + const context = this.getContext(); + if (context?.route) { + this.activateWith(context.route, context.injector); + } + } + + new Promise((resolve) => componentOnReady(this.nativeEl, resolve)).then(() => { + if (this._swipeGesture === undefined) { + this.swipeGesture = this.config.getBoolean('swipeBackEnabled', (this.nativeEl as any).mode === 'ios'); + } + }); + } + + get isActivated(): boolean { + return !!this.activated; + } + + get component(): Record { + if (!this.activated) { + throw new Error('Outlet is not activated'); + } + return this.activated.instance; + } + + get activatedRoute(): ActivatedRoute { + if (!this.activated) { + throw new Error('Outlet is not activated'); + } + return this._activatedRoute as ActivatedRoute; + } + + get activatedRouteData(): Data { + if (this._activatedRoute) { + return this._activatedRoute.snapshot.data; + } + return {}; + } + + /** + * Called when the `RouteReuseStrategy` instructs to detach the subtree + */ + detach(): ComponentRef { + throw new Error('incompatible reuse strategy'); + } + + /** + * Called when the `RouteReuseStrategy` instructs to re-attach a previously detached subtree + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + attach(_ref: ComponentRef, _activatedRoute: ActivatedRoute): void { + throw new Error('incompatible reuse strategy'); + } + + deactivate(): void { + if (this.activated) { + if (this.activatedView) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const context = this.getContext()!; + this.activatedView.savedData = new Map(context.children['contexts']); + + /** + * Angular v11.2.10 introduced a change + * where this route context is cleared out when + * a router-outlet is deactivated, However, + * we need this route information in order to + * return a user back to the correct tab when + * leaving and then going back to the tab context. + */ + const primaryOutlet = this.activatedView.savedData.get('primary'); + if (primaryOutlet && context.route) { + primaryOutlet.route = { ...context.route }; + } + + /** + * Ensure we are saving the NavigationExtras + * data otherwise it will be lost + */ + this.activatedView.savedExtras = {}; + if (context.route) { + const contextSnapshot = context.route.snapshot; + + this.activatedView.savedExtras.queryParams = contextSnapshot.queryParams; + (this.activatedView.savedExtras.fragment as string | null) = contextSnapshot.fragment; + } + } + const c = this.component; + this.activatedView = null; + this.activated = null; + this._activatedRoute = null; + this.deactivateEvents.emit(c); + } + } + + activateWith(activatedRoute: ActivatedRoute, environmentInjector: EnvironmentInjector | null): void { + if (this.isActivated) { + throw new Error('Cannot activate an already activated outlet'); + } + this._activatedRoute = activatedRoute; + + let cmpRef: any; + let enteringView = this.stackCtrl.getExistingView(activatedRoute); + if (enteringView) { + cmpRef = this.activated = enteringView.ref; + const saved = enteringView.savedData; + if (saved) { + // self-restore + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const context = this.getContext()!; + context.children['contexts'] = saved; + } + // Updated activated route proxy for this component + this.updateActivatedRouteProxy(cmpRef.instance, activatedRoute); + } else { + const snapshot = (activatedRoute as any)._futureSnapshot; + + /** + * Angular 14 introduces a new `loadComponent` property to the route config. + * This function will assign a `component` property to the route snapshot. + * We check for the presence of this property to determine if the route is + * using standalone components. + */ + const childContexts = this.parentContexts.getOrCreateContext(this.name).children; + + // We create an activated route proxy object that will maintain future updates for this component + // over its lifecycle in the stack. + const component$ = new BehaviorSubject(null); + const activatedRouteProxy = this.createActivatedRouteProxy(component$, activatedRoute); + + const injector = new OutletInjector(activatedRouteProxy, childContexts, this.location.injector); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const component = snapshot.routeConfig!.component ?? snapshot.component; + + cmpRef = this.activated = this.location.createComponent(component, { + index: this.location.length, + injector, + environmentInjector: environmentInjector ?? this.environmentInjector, + }); + + // Once the component is created we can push it to our local subject supplied to the proxy + component$.next(cmpRef.instance); + + // Calling `markForCheck` to make sure we will run the change detection when the + // `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component. + enteringView = this.stackCtrl.createView(this.activated, activatedRoute); + + // Store references to the proxy by component + this.proxyMap.set(cmpRef.instance, activatedRouteProxy); + this.currentActivatedRoute$.next({ component: cmpRef.instance, activatedRoute }); + } + + this.inputBinder?.bindActivatedRouteToOutletComponent(this); + + this.activatedView = enteringView; + + /** + * The top outlet is set prior to the entering view's transition completing, + * so that when we have nested outlets (e.g. ion-tabs inside an ion-router-outlet), + * the tabs outlet will be assigned as the top outlet when a view inside tabs is + * activated. + * + * In this scenario, activeWith is called for both the tabs and the root router outlet. + * To avoid a race condition, we assign the top outlet synchronously. + */ + this.navCtrl.setTopOutlet(this); + + this.stackCtrl.setActive(enteringView).then((data) => { + this.activateEvents.emit(cmpRef.instance); + this.stackEvents.emit(data); + }); + } + + /** + * Returns `true` if there are pages in the stack to go back. + */ + canGoBack(deep = 1, stackId?: string): boolean { + return this.stackCtrl.canGoBack(deep, stackId); + } + + /** + * Resolves to `true` if it the outlet was able to sucessfully pop the last N pages. + */ + pop(deep = 1, stackId?: string): Promise { + return this.stackCtrl.pop(deep, stackId); + } + + /** + * Returns the URL of the active page of each stack. + */ + getLastUrl(stackId?: string): string | undefined { + const active = this.stackCtrl.getLastUrl(stackId); + return active ? active.url : undefined; + } + + /** + * Returns the RouteView of the active page of each stack. + * @internal + */ + getLastRouteView(stackId?: string): RouteView | undefined { + return this.stackCtrl.getLastUrl(stackId); + } + + /** + * Returns the root view in the tab stack. + * @internal + */ + getRootView(stackId?: string): RouteView | undefined { + return this.stackCtrl.getRootUrl(stackId); + } + + /** + * Returns the active stack ID. In the context of ion-tabs, it means the active tab. + */ + getActiveStackId(): string | undefined { + return this.stackCtrl.getActiveStackId(); + } + + /** + * Since the activated route can change over the life time of a component in an ion router outlet, we create + * a proxy so that we can update the values over time as a user navigates back to components already in the stack. + */ + private createActivatedRouteProxy(component$: Observable, activatedRoute: ActivatedRoute): ActivatedRoute { + const proxy: any = new ActivatedRoute(); + + proxy._futureSnapshot = (activatedRoute as any)._futureSnapshot; + proxy._routerState = (activatedRoute as any)._routerState; + proxy.snapshot = activatedRoute.snapshot; + proxy.outlet = activatedRoute.outlet; + proxy.component = activatedRoute.component; + + // Setup wrappers for the observables so consumers don't have to worry about switching to new observables as the state updates + (proxy as any)._paramMap = this.proxyObservable(component$, 'paramMap'); + (proxy as any)._queryParamMap = this.proxyObservable(component$, 'queryParamMap'); + proxy.url = this.proxyObservable(component$, 'url'); + proxy.params = this.proxyObservable(component$, 'params'); + proxy.queryParams = this.proxyObservable(component$, 'queryParams'); + proxy.fragment = this.proxyObservable(component$, 'fragment'); + proxy.data = this.proxyObservable(component$, 'data'); + + return proxy as ActivatedRoute; + } + + /** + * Create a wrapped observable that will switch to the latest activated route matched by the given component + */ + private proxyObservable(component$: Observable, path: string): Observable { + return component$.pipe( + // First wait until the component instance is pushed + filter((component) => !!component), + switchMap((component) => + this.currentActivatedRoute$.pipe( + filter((current) => current !== null && current.component === component), + switchMap((current) => current && (current.activatedRoute as any)[path]), + distinctUntilChanged() + ) + ) + ); + } + + /** + * Updates the activated route proxy for the given component to the new incoming router state + */ + private updateActivatedRouteProxy(component: any, activatedRoute: ActivatedRoute): void { + const proxy = this.proxyMap.get(component); + if (!proxy) { + throw new Error(`Could not find activated route proxy for view`); + } + + (proxy as any)._futureSnapshot = (activatedRoute as any)._futureSnapshot; + (proxy as any)._routerState = (activatedRoute as any)._routerState; + proxy.snapshot = activatedRoute.snapshot; + proxy.outlet = activatedRoute.outlet; + proxy.component = activatedRoute.component; + + this.currentActivatedRoute$.next({ component, activatedRoute }); + } +} + +class OutletInjector implements Injector { + constructor(private route: ActivatedRoute, private childContexts: ChildrenOutletContexts, private parent: Injector) {} + + get(token: any, notFoundValue?: any): any { + if (token === ActivatedRoute) { + return this.route; + } + + if (token === ChildrenOutletContexts) { + return this.childContexts; + } + + return this.parent.get(token, notFoundValue); + } +} + +// TODO: FW-4785 - Remove this once Angular 15 support is dropped +export const INPUT_BINDER = new InjectionToken(''); + +/** + * Injectable used as a tree-shakable provider for opting in to binding router data to component + * inputs. + * + * The RouterOutlet registers itself with this service when an `ActivatedRoute` is attached or + * activated. When this happens, the service subscribes to the `ActivatedRoute` observables (params, + * queryParams, data) and sets the inputs of the component using `ComponentRef.setInput`. + * Importantly, when an input does not have an item in the route data with a matching key, this + * input is set to `undefined`. If it were not done this way, the previous information would be + * retained if the data got removed from the route (i.e. if a query parameter is removed). + * + * The `RouterOutlet` should unregister itself when destroyed via `unsubscribeFromRouteData` so that + * the subscriptions are cleaned up. + */ +@Injectable() +export class RoutedComponentInputBinder { + private outletDataSubscriptions = new Map(); + + bindActivatedRouteToOutletComponent(outlet: IonRouterOutlet): void { + this.unsubscribeFromRouteData(outlet); + this.subscribeToRouteData(outlet); + } + + unsubscribeFromRouteData(outlet: IonRouterOutlet): void { + this.outletDataSubscriptions.get(outlet)?.unsubscribe(); + this.outletDataSubscriptions.delete(outlet); + } + + private subscribeToRouteData(outlet: IonRouterOutlet) { + const { activatedRoute } = outlet; + const dataSubscription = combineLatest([activatedRoute.queryParams, activatedRoute.params, activatedRoute.data]) + .pipe( + switchMap(([queryParams, params, data], index) => { + data = { ...queryParams, ...params, ...data }; + // Get the first result from the data subscription synchronously so it's available to + // the component as soon as possible (and doesn't require a second change detection). + if (index === 0) { + return of(data); + } + // Promise.resolve is used to avoid synchronously writing the wrong data when + // two of the Observables in the `combineLatest` stream emit one after + // another. + return Promise.resolve(data); + }) + ) + .subscribe((data) => { + // Outlet may have been deactivated or changed names to be associated with a different + // route + if ( + !outlet.isActivated || + !outlet.activatedComponentRef || + outlet.activatedRoute !== activatedRoute || + activatedRoute.component === null + ) { + this.unsubscribeFromRouteData(outlet); + return; + } + + const mirror = reflectComponentType(activatedRoute.component); + if (!mirror) { + this.unsubscribeFromRouteData(outlet); + return; + } + + for (const { templateName } of mirror.inputs) { + outlet.activatedComponentRef.setInput(templateName, data[templateName]); + } + }); + + this.outletDataSubscriptions.set(outlet, dataSubscription); + } +} diff --git a/packages/angular/src/directives/navigation/stack-controller.ts b/packages/angular/common/src/directives/navigation/stack-controller.ts similarity index 98% rename from packages/angular/src/directives/navigation/stack-controller.ts rename to packages/angular/common/src/directives/navigation/stack-controller.ts index 809b67315f4..d44c4d0e86e 100644 --- a/packages/angular/src/directives/navigation/stack-controller.ts +++ b/packages/angular/common/src/directives/navigation/stack-controller.ts @@ -1,9 +1,9 @@ import { Location } from '@angular/common'; import { ComponentRef, NgZone } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { bindLifecycleEvents } from '@ionic/angular/common'; -import { AnimationBuilder, RouterDirection } from '@ionic/core'; +import type { AnimationBuilder, RouterDirection } from '@ionic/core/components'; +import { bindLifecycleEvents } from '../../providers/angular-delegate'; import { NavController } from '../../providers/nav-controller'; import { diff --git a/packages/angular/src/directives/navigation/stack-utils.ts b/packages/angular/common/src/directives/navigation/stack-utils.ts similarity index 96% rename from packages/angular/src/directives/navigation/stack-utils.ts rename to packages/angular/common/src/directives/navigation/stack-utils.ts index ae33e03ce7a..1dc539e25d0 100644 --- a/packages/angular/src/directives/navigation/stack-utils.ts +++ b/packages/angular/common/src/directives/navigation/stack-utils.ts @@ -1,6 +1,6 @@ import { ComponentRef } from '@angular/core'; import { ActivatedRoute, NavigationExtras, Router } from '@angular/router'; -import { AnimationBuilder, NavDirection, RouterDirection } from '@ionic/core'; +import type { AnimationBuilder, NavDirection, RouterDirection } from '@ionic/core/components'; export const insertView = (views: RouteView[], view: RouteView, direction: RouterDirection): RouteView[] => { if (direction === 'root') { diff --git a/packages/angular/common/src/directives/navigation/tabs.ts b/packages/angular/common/src/directives/navigation/tabs.ts new file mode 100644 index 00000000000..9807ea8bed7 --- /dev/null +++ b/packages/angular/common/src/directives/navigation/tabs.ts @@ -0,0 +1,177 @@ +import { + AfterContentChecked, + AfterContentInit, + Directive, + ElementRef, + EventEmitter, + HostListener, + Output, + ViewChild, +} from '@angular/core'; + +import { NavController } from '../../providers/nav-controller'; + +import type { StackEvent } from './stack-utils'; + +@Directive({ + selector: 'ion-tabs', +}) +// eslint-disable-next-line @angular-eslint/directive-class-suffix +export class IonTabs implements AfterContentInit, AfterContentChecked { + /** + * Note: These must be redeclared on each child class since it needs + * access to generated components such as IonRouterOutlet and IonTabBar. + */ + outlet: any; + tabBar: any; + tabBars: any; + + @ViewChild('tabsInner', { read: ElementRef, static: true }) tabsInner: ElementRef; + + @Output() ionTabsWillChange = new EventEmitter<{ tab: string }>(); + @Output() ionTabsDidChange = new EventEmitter<{ tab: string }>(); + + private tabBarSlot = 'bottom'; + + constructor(private navCtrl: NavController) {} + + ngAfterContentInit(): void { + this.detectSlotChanges(); + } + + ngAfterContentChecked(): void { + this.detectSlotChanges(); + } + + /** + * @internal + */ + onPageSelected(detail: StackEvent): void { + const stackId = detail.enteringView.stackId; + if (detail.tabSwitch && stackId !== undefined) { + this.ionTabsWillChange.emit({ tab: stackId }); + if (this.tabBar) { + this.tabBar.selectedTab = stackId; + } + this.ionTabsDidChange.emit({ tab: stackId }); + } + } + + /** + * When a tab button is clicked, there are several scenarios: + * 1. If the selected tab is currently active (the tab button has been clicked + * again), then it should go to the root view for that tab. + * + * a. Get the saved root view from the router outlet. If the saved root view + * matches the tabRootUrl, set the route view to this view including the + * navigation extras. + * b. If the saved root view from the router outlet does + * not match, navigate to the tabRootUrl. No navigation extras are + * included. + * + * 2. If the current tab tab is not currently selected, get the last route + * view from the router outlet. + * + * a. If the last route view exists, navigate to that view including any + * navigation extras + * b. If the last route view doesn't exist, then navigate + * to the default tabRootUrl + */ + @HostListener('ionTabButtonClick', ['$event']) + select(tabOrEvent: string | CustomEvent): Promise | undefined { + const isTabString = typeof tabOrEvent === 'string'; + const tab = isTabString ? tabOrEvent : (tabOrEvent as CustomEvent).detail.tab; + const alreadySelected = this.outlet.getActiveStackId() === tab; + const tabRootUrl = `${this.outlet.tabsPrefix}/${tab}`; + + /** + * If this is a nested tab, prevent the event + * from bubbling otherwise the outer tabs + * will respond to this event too, causing + * the app to get directed to the wrong place. + */ + if (!isTabString) { + (tabOrEvent as CustomEvent).stopPropagation(); + } + + if (alreadySelected) { + const activeStackId = this.outlet.getActiveStackId(); + const activeView = this.outlet.getLastRouteView(activeStackId); + + // If on root tab, do not navigate to root tab again + if (activeView?.url === tabRootUrl) { + return; + } + + const rootView = this.outlet.getRootView(tab); + const navigationExtras = rootView && tabRootUrl === rootView.url && rootView.savedExtras; + return this.navCtrl.navigateRoot(tabRootUrl, { + ...navigationExtras, + animated: true, + animationDirection: 'back', + }); + } else { + const lastRoute = this.outlet.getLastRouteView(tab); + /** + * If there is a lastRoute, goto that, otherwise goto the fallback url of the + * selected tab + */ + const url = lastRoute?.url || tabRootUrl; + const navigationExtras = lastRoute?.savedExtras; + + return this.navCtrl.navigateRoot(url, { + ...navigationExtras, + animated: true, + animationDirection: 'back', + }); + } + } + + getSelected(): string | undefined { + return this.outlet.getActiveStackId(); + } + + /** + * Detects changes to the slot attribute of the tab bar. + * + * If the slot attribute has changed, then the tab bar + * should be relocated to the new slot position. + */ + private detectSlotChanges(): void { + this.tabBars.forEach((tabBar: any) => { + // el is a protected attribute from the generated component wrapper + const currentSlot = tabBar.el.getAttribute('slot'); + + if (currentSlot !== this.tabBarSlot) { + this.tabBarSlot = currentSlot; + this.relocateTabBar(); + } + }); + } + + /** + * Relocates the tab bar to the new slot position. + */ + private relocateTabBar(): void { + /** + * `el` is a protected attribute from the generated component wrapper. + * To avoid having to manually create the wrapper for tab bar, we + * cast the tab bar to any and access the protected attribute. + */ + const tabBar = (this.tabBar as any).el as HTMLElement; + + if (this.tabBarSlot === 'top') { + /** + * A tab bar with a slot of "top" should be inserted + * at the top of the container. + */ + this.tabsInner.nativeElement.before(tabBar); + } else { + /** + * A tab bar with a slot of "bottom" or without a slot + * should be inserted at the end of the container. + */ + this.tabsInner.nativeElement.after(tabBar); + } + } +} diff --git a/packages/angular/common/src/index.ts b/packages/angular/common/src/index.ts index 9ed4b009796..74bc82f3c80 100644 --- a/packages/angular/common/src/index.ts +++ b/packages/angular/common/src/index.ts @@ -10,6 +10,7 @@ export { ToastController } from './providers/toast-controller'; export { AnimationController } from './providers/animation-controller'; export { GestureController } from './providers/gesture-controller'; export { DomController } from './providers/dom-controller'; +export { NavController } from './providers/nav-controller'; export { Config, ConfigToken } from './providers/config'; export { Platform } from './providers/platform'; @@ -19,3 +20,13 @@ export { bindLifecycleEvents, AngularDelegate } from './providers/angular-delega export type { IonicWindow } from './types/interfaces'; export { NavParams } from './directives/navigation/nav-params'; +export { IonRouterOutlet, INPUT_BINDER, RoutedComponentInputBinder } from './directives/navigation/router-outlet'; +export type { StackEvent } from './directives/navigation/stack-utils'; + +export { IonBackButton } from './directives/navigation/back-button'; +export { + RouterLinkDelegateDirective, + RouterLinkWithHrefDelegateDirective, +} from './directives/navigation/router-link-delegate'; +export { NavDelegate } from './directives/navigation/nav-delegate'; +export { IonTabs } from './directives/navigation/tabs'; diff --git a/packages/angular/src/providers/nav-controller.ts b/packages/angular/common/src/providers/nav-controller.ts similarity index 97% rename from packages/angular/src/providers/nav-controller.ts rename to packages/angular/common/src/providers/nav-controller.ts index b5859d52496..aff31b090b3 100644 --- a/packages/angular/src/providers/nav-controller.ts +++ b/packages/angular/common/src/providers/nav-controller.ts @@ -1,10 +1,11 @@ import { Location } from '@angular/common'; import { Injectable, Optional } from '@angular/core'; import { NavigationExtras, Router, UrlSerializer, UrlTree, NavigationStart } from '@angular/router'; -import { Platform } from '@ionic/angular/common'; -import { AnimationBuilder, NavDirection, RouterDirection } from '@ionic/core'; +import type { AnimationBuilder, NavDirection, RouterDirection } from '@ionic/core/components'; -import { IonRouterOutlet } from '../directives/navigation/ion-router-outlet'; +import { IonRouterOutlet } from '../directives/navigation/router-outlet'; + +import { Platform } from './platform'; export interface AnimationOptions { animated?: boolean; diff --git a/packages/angular/common/src/utils/proxy.ts b/packages/angular/common/src/utils/proxy.ts new file mode 100644 index 00000000000..b9070978db1 --- /dev/null +++ b/packages/angular/common/src/utils/proxy.ts @@ -0,0 +1,53 @@ +// TODO: Is there a way we can grab this from angular-component-lib instead? + +/* eslint-disable */ +/* tslint:disable */ +import { fromEvent } from 'rxjs'; + +export const proxyInputs = (Cmp: any, inputs: string[]) => { + const Prototype = Cmp.prototype; + inputs.forEach((item) => { + Object.defineProperty(Prototype, item, { + get() { + return this.el[item]; + }, + set(val: any) { + this.z.runOutsideAngular(() => (this.el[item] = val)); + }, + }); + }); +}; + +export const proxyMethods = (Cmp: any, methods: string[]) => { + const Prototype = Cmp.prototype; + methods.forEach((methodName) => { + Prototype[methodName] = function () { + const args = arguments; + return this.z.runOutsideAngular(() => this.el[methodName].apply(this.el, args)); + }; + }); +}; + +export const proxyOutputs = (instance: any, el: any, events: string[]) => { + events.forEach((eventName) => (instance[eventName] = fromEvent(el, eventName))); +}; + +// tslint:disable-next-line: only-arrow-functions +export function ProxyCmp(opts: { defineCustomElementFn?: () => void; inputs?: any; methods?: any }) { + const decorator = function (cls: any) { + const { defineCustomElementFn, inputs, methods } = opts; + + if (defineCustomElementFn !== undefined) { + defineCustomElementFn(); + } + + if (inputs) { + proxyInputs(cls, inputs); + } + if (methods) { + proxyMethods(cls, methods); + } + return cls; + }; + return decorator; +} diff --git a/packages/angular/src/directives/navigation/ion-back-button.ts b/packages/angular/src/directives/navigation/ion-back-button.ts index 210096f598c..fbf14d7fb5a 100644 --- a/packages/angular/src/directives/navigation/ion-back-button.ts +++ b/packages/angular/src/directives/navigation/ion-back-button.ts @@ -1,41 +1,14 @@ -import { Directive, HostListener, Input, Optional } from '@angular/core'; -import { Config } from '@ionic/angular/common'; -import { AnimationBuilder } from '@ionic/core'; - -import { NavController } from '../../providers/nav-controller'; +import { Directive, Optional } from '@angular/core'; +import { IonBackButton as IonBackButtonBase, NavController, Config } from '@ionic/angular/common'; import { IonRouterOutlet } from './ion-router-outlet'; @Directive({ selector: 'ion-back-button', }) -export class IonBackButtonDelegateDirective { - @Input() - defaultHref: string | undefined | null; - - @Input() - routerAnimation?: AnimationBuilder; - - constructor( - @Optional() private routerOutlet: IonRouterOutlet, - private navCtrl: NavController, - private config: Config - ) {} - - /** - * @internal - */ - @HostListener('click', ['$event']) - onClick(ev: Event): void { - const defaultHref = this.defaultHref || this.config.get('backButtonDefaultHref'); - - if (this.routerOutlet?.canGoBack()) { - this.navCtrl.setDirection('back', undefined, undefined, this.routerAnimation); - this.routerOutlet.pop(); - ev.preventDefault(); - } else if (defaultHref != null) { - this.navCtrl.navigateBack(defaultHref, { animation: this.routerAnimation }); - ev.preventDefault(); - } +// eslint-disable-next-line @angular-eslint/directive-class-suffix +export class IonBackButtonDelegateDirective extends IonBackButtonBase { + constructor(@Optional() routerOutlet: IonRouterOutlet, navCtrl: NavController, config: Config) { + super(routerOutlet, navCtrl, config); } } diff --git a/packages/angular/src/directives/navigation/ion-router-outlet.ts b/packages/angular/src/directives/navigation/ion-router-outlet.ts index cef96a1c67b..2187f28b376 100644 --- a/packages/angular/src/directives/navigation/ion-router-outlet.ts +++ b/packages/angular/src/directives/navigation/ion-router-outlet.ts @@ -1,509 +1,8 @@ -import { Location } from '@angular/common'; -import { - ComponentRef, - ElementRef, - Injector, - NgZone, - OnDestroy, - OnInit, - ViewContainerRef, - inject, - Attribute, - Directive, - EventEmitter, - Optional, - Output, - SkipSelf, - EnvironmentInjector, - Input, - InjectionToken, - Injectable, - reflectComponentType, -} from '@angular/core'; -import { OutletContext, Router, ActivatedRoute, ChildrenOutletContexts, PRIMARY_OUTLET, Data } from '@angular/router'; -import { Config } from '@ionic/angular/common'; -import { componentOnReady } from '@ionic/core'; -import { Observable, BehaviorSubject, Subscription, combineLatest, of } from 'rxjs'; -import { distinctUntilChanged, filter, switchMap } from 'rxjs/operators'; - -import { AnimationBuilder } from '../../ionic-core'; -import { NavController } from '../../providers/nav-controller'; - -import { StackController } from './stack-controller'; -import { RouteView, getUrl } from './stack-utils'; - -// TODO(FW-2827): types +import { Directive } from '@angular/core'; +import { IonRouterOutlet as IonRouterOutletBase } from '@ionic/angular/common'; @Directive({ selector: 'ion-router-outlet', - exportAs: 'outlet', - // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'animation', 'mode', 'swipeGesture'], }) // eslint-disable-next-line @angular-eslint/directive-class-suffix -export class IonRouterOutlet implements OnDestroy, OnInit { - nativeEl: HTMLIonRouterOutletElement; - activatedView: RouteView | null = null; - tabsPrefix: string | undefined; - - private _swipeGesture?: boolean; - private stackCtrl: StackController; - - // Maintain map of activated route proxies for each component instance - private proxyMap = new WeakMap(); - // Keep the latest activated route in a subject for the proxy routes to switch map to - private currentActivatedRoute$ = new BehaviorSubject<{ component: any; activatedRoute: ActivatedRoute } | null>(null); - - private activated: ComponentRef | null = null; - /** @internal */ - get activatedComponentRef(): ComponentRef | null { - return this.activated; - } - private _activatedRoute: ActivatedRoute | null = null; - - /** - * The name of the outlet - */ - @Input() name = PRIMARY_OUTLET; - - @Output() stackEvents = new EventEmitter(); - // eslint-disable-next-line @angular-eslint/no-output-rename - @Output('activate') activateEvents = new EventEmitter(); - // eslint-disable-next-line @angular-eslint/no-output-rename - @Output('deactivate') deactivateEvents = new EventEmitter(); - - private parentContexts = inject(ChildrenOutletContexts); - private location = inject(ViewContainerRef); - private environmentInjector = inject(EnvironmentInjector); - private inputBinder = inject(INPUT_BINDER, { optional: true }); - /** @nodoc */ - readonly supportsBindingToComponentInputs = true; - - // Ionic providers - private config = inject(Config); - private navCtrl = inject(NavController); - - set animation(animation: AnimationBuilder) { - this.nativeEl.animation = animation; - } - - set animated(animated: boolean) { - this.nativeEl.animated = animated; - } - - set swipeGesture(swipe: boolean) { - this._swipeGesture = swipe; - - this.nativeEl.swipeHandler = swipe - ? { - canStart: () => this.stackCtrl.canGoBack(1) && !this.stackCtrl.hasRunningTask(), - onStart: () => this.stackCtrl.startBackTransition(), - onEnd: (shouldContinue) => this.stackCtrl.endBackTransition(shouldContinue), - } - : undefined; - } - - constructor( - @Attribute('name') name: string, - @Optional() @Attribute('tabs') tabs: string, - commonLocation: Location, - elementRef: ElementRef, - router: Router, - zone: NgZone, - activatedRoute: ActivatedRoute, - @SkipSelf() @Optional() readonly parentOutlet?: IonRouterOutlet - ) { - this.nativeEl = elementRef.nativeElement; - this.name = name || PRIMARY_OUTLET; - this.tabsPrefix = tabs === 'true' ? getUrl(router, activatedRoute) : undefined; - this.stackCtrl = new StackController(this.tabsPrefix, this.nativeEl, router, this.navCtrl, zone, commonLocation); - this.parentContexts.onChildOutletCreated(this.name, this as any); - } - - ngOnDestroy(): void { - this.stackCtrl.destroy(); - this.inputBinder?.unsubscribeFromRouteData(this); - } - - getContext(): OutletContext | null { - return this.parentContexts.getContext(this.name); - } - - ngOnInit(): void { - this.initializeOutletWithName(); - } - - // Note: Ionic deviates from the Angular Router implementation here - private initializeOutletWithName() { - if (!this.activated) { - // If the outlet was not instantiated at the time the route got activated we need to populate - // the outlet when it is initialized (ie inside a NgIf) - const context = this.getContext(); - if (context?.route) { - this.activateWith(context.route, context.injector); - } - } - - new Promise((resolve) => componentOnReady(this.nativeEl, resolve)).then(() => { - if (this._swipeGesture === undefined) { - this.swipeGesture = this.config.getBoolean('swipeBackEnabled', (this.nativeEl as any).mode === 'ios'); - } - }); - } - - get isActivated(): boolean { - return !!this.activated; - } - - get component(): Record { - if (!this.activated) { - throw new Error('Outlet is not activated'); - } - return this.activated.instance; - } - - get activatedRoute(): ActivatedRoute { - if (!this.activated) { - throw new Error('Outlet is not activated'); - } - return this._activatedRoute as ActivatedRoute; - } - - get activatedRouteData(): Data { - if (this._activatedRoute) { - return this._activatedRoute.snapshot.data; - } - return {}; - } - - /** - * Called when the `RouteReuseStrategy` instructs to detach the subtree - */ - detach(): ComponentRef { - throw new Error('incompatible reuse strategy'); - } - - /** - * Called when the `RouteReuseStrategy` instructs to re-attach a previously detached subtree - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - attach(_ref: ComponentRef, _activatedRoute: ActivatedRoute): void { - throw new Error('incompatible reuse strategy'); - } - - deactivate(): void { - if (this.activated) { - if (this.activatedView) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const context = this.getContext()!; - this.activatedView.savedData = new Map(context.children['contexts']); - - /** - * Angular v11.2.10 introduced a change - * where this route context is cleared out when - * a router-outlet is deactivated, However, - * we need this route information in order to - * return a user back to the correct tab when - * leaving and then going back to the tab context. - */ - const primaryOutlet = this.activatedView.savedData.get('primary'); - if (primaryOutlet && context.route) { - primaryOutlet.route = { ...context.route }; - } - - /** - * Ensure we are saving the NavigationExtras - * data otherwise it will be lost - */ - this.activatedView.savedExtras = {}; - if (context.route) { - const contextSnapshot = context.route.snapshot; - - this.activatedView.savedExtras.queryParams = contextSnapshot.queryParams; - (this.activatedView.savedExtras.fragment as string | null) = contextSnapshot.fragment; - } - } - const c = this.component; - this.activatedView = null; - this.activated = null; - this._activatedRoute = null; - this.deactivateEvents.emit(c); - } - } - - activateWith(activatedRoute: ActivatedRoute, environmentInjector: EnvironmentInjector | null): void { - if (this.isActivated) { - throw new Error('Cannot activate an already activated outlet'); - } - this._activatedRoute = activatedRoute; - - let cmpRef: any; - let enteringView = this.stackCtrl.getExistingView(activatedRoute); - if (enteringView) { - cmpRef = this.activated = enteringView.ref; - const saved = enteringView.savedData; - if (saved) { - // self-restore - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const context = this.getContext()!; - context.children['contexts'] = saved; - } - // Updated activated route proxy for this component - this.updateActivatedRouteProxy(cmpRef.instance, activatedRoute); - } else { - const snapshot = (activatedRoute as any)._futureSnapshot; - - /** - * Angular 14 introduces a new `loadComponent` property to the route config. - * This function will assign a `component` property to the route snapshot. - * We check for the presence of this property to determine if the route is - * using standalone components. - */ - const childContexts = this.parentContexts.getOrCreateContext(this.name).children; - - // We create an activated route proxy object that will maintain future updates for this component - // over its lifecycle in the stack. - const component$ = new BehaviorSubject(null); - const activatedRouteProxy = this.createActivatedRouteProxy(component$, activatedRoute); - - const injector = new OutletInjector(activatedRouteProxy, childContexts, this.location.injector); - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const component = snapshot.routeConfig!.component ?? snapshot.component; - - cmpRef = this.activated = this.location.createComponent(component, { - index: this.location.length, - injector, - environmentInjector: environmentInjector ?? this.environmentInjector, - }); - - // Once the component is created we can push it to our local subject supplied to the proxy - component$.next(cmpRef.instance); - - // Calling `markForCheck` to make sure we will run the change detection when the - // `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component. - enteringView = this.stackCtrl.createView(this.activated, activatedRoute); - - // Store references to the proxy by component - this.proxyMap.set(cmpRef.instance, activatedRouteProxy); - this.currentActivatedRoute$.next({ component: cmpRef.instance, activatedRoute }); - } - - this.inputBinder?.bindActivatedRouteToOutletComponent(this); - - this.activatedView = enteringView; - - /** - * The top outlet is set prior to the entering view's transition completing, - * so that when we have nested outlets (e.g. ion-tabs inside an ion-router-outlet), - * the tabs outlet will be assigned as the top outlet when a view inside tabs is - * activated. - * - * In this scenario, activeWith is called for both the tabs and the root router outlet. - * To avoid a race condition, we assign the top outlet synchronously. - */ - this.navCtrl.setTopOutlet(this); - - this.stackCtrl.setActive(enteringView).then((data) => { - this.activateEvents.emit(cmpRef.instance); - this.stackEvents.emit(data); - }); - } - - /** - * Returns `true` if there are pages in the stack to go back. - */ - canGoBack(deep = 1, stackId?: string): boolean { - return this.stackCtrl.canGoBack(deep, stackId); - } - - /** - * Resolves to `true` if it the outlet was able to sucessfully pop the last N pages. - */ - pop(deep = 1, stackId?: string): Promise { - return this.stackCtrl.pop(deep, stackId); - } - - /** - * Returns the URL of the active page of each stack. - */ - getLastUrl(stackId?: string): string | undefined { - const active = this.stackCtrl.getLastUrl(stackId); - return active ? active.url : undefined; - } - - /** - * Returns the RouteView of the active page of each stack. - * @internal - */ - getLastRouteView(stackId?: string): RouteView | undefined { - return this.stackCtrl.getLastUrl(stackId); - } - - /** - * Returns the root view in the tab stack. - * @internal - */ - getRootView(stackId?: string): RouteView | undefined { - return this.stackCtrl.getRootUrl(stackId); - } - - /** - * Returns the active stack ID. In the context of ion-tabs, it means the active tab. - */ - getActiveStackId(): string | undefined { - return this.stackCtrl.getActiveStackId(); - } - - /** - * Since the activated route can change over the life time of a component in an ion router outlet, we create - * a proxy so that we can update the values over time as a user navigates back to components already in the stack. - */ - private createActivatedRouteProxy(component$: Observable, activatedRoute: ActivatedRoute): ActivatedRoute { - const proxy: any = new ActivatedRoute(); - - proxy._futureSnapshot = (activatedRoute as any)._futureSnapshot; - proxy._routerState = (activatedRoute as any)._routerState; - proxy.snapshot = activatedRoute.snapshot; - proxy.outlet = activatedRoute.outlet; - proxy.component = activatedRoute.component; - - // Setup wrappers for the observables so consumers don't have to worry about switching to new observables as the state updates - (proxy as any)._paramMap = this.proxyObservable(component$, 'paramMap'); - (proxy as any)._queryParamMap = this.proxyObservable(component$, 'queryParamMap'); - proxy.url = this.proxyObservable(component$, 'url'); - proxy.params = this.proxyObservable(component$, 'params'); - proxy.queryParams = this.proxyObservable(component$, 'queryParams'); - proxy.fragment = this.proxyObservable(component$, 'fragment'); - proxy.data = this.proxyObservable(component$, 'data'); - - return proxy as ActivatedRoute; - } - - /** - * Create a wrapped observable that will switch to the latest activated route matched by the given component - */ - private proxyObservable(component$: Observable, path: string): Observable { - return component$.pipe( - // First wait until the component instance is pushed - filter((component) => !!component), - switchMap((component) => - this.currentActivatedRoute$.pipe( - filter((current) => current !== null && current.component === component), - switchMap((current) => current && (current.activatedRoute as any)[path]), - distinctUntilChanged() - ) - ) - ); - } - - /** - * Updates the activated route proxy for the given component to the new incoming router state - */ - private updateActivatedRouteProxy(component: any, activatedRoute: ActivatedRoute): void { - const proxy = this.proxyMap.get(component); - if (!proxy) { - throw new Error(`Could not find activated route proxy for view`); - } - - (proxy as any)._futureSnapshot = (activatedRoute as any)._futureSnapshot; - (proxy as any)._routerState = (activatedRoute as any)._routerState; - proxy.snapshot = activatedRoute.snapshot; - proxy.outlet = activatedRoute.outlet; - proxy.component = activatedRoute.component; - - this.currentActivatedRoute$.next({ component, activatedRoute }); - } -} - -class OutletInjector implements Injector { - constructor(private route: ActivatedRoute, private childContexts: ChildrenOutletContexts, private parent: Injector) {} - - get(token: any, notFoundValue?: any): any { - if (token === ActivatedRoute) { - return this.route; - } - - if (token === ChildrenOutletContexts) { - return this.childContexts; - } - - return this.parent.get(token, notFoundValue); - } -} - -// TODO: FW-4785 - Remove this once Angular 15 support is dropped -export const INPUT_BINDER = new InjectionToken(''); - -/** - * Injectable used as a tree-shakable provider for opting in to binding router data to component - * inputs. - * - * The RouterOutlet registers itself with this service when an `ActivatedRoute` is attached or - * activated. When this happens, the service subscribes to the `ActivatedRoute` observables (params, - * queryParams, data) and sets the inputs of the component using `ComponentRef.setInput`. - * Importantly, when an input does not have an item in the route data with a matching key, this - * input is set to `undefined`. If it were not done this way, the previous information would be - * retained if the data got removed from the route (i.e. if a query parameter is removed). - * - * The `RouterOutlet` should unregister itself when destroyed via `unsubscribeFromRouteData` so that - * the subscriptions are cleaned up. - */ -@Injectable() -export class RoutedComponentInputBinder { - private outletDataSubscriptions = new Map(); - - bindActivatedRouteToOutletComponent(outlet: IonRouterOutlet): void { - this.unsubscribeFromRouteData(outlet); - this.subscribeToRouteData(outlet); - } - - unsubscribeFromRouteData(outlet: IonRouterOutlet): void { - this.outletDataSubscriptions.get(outlet)?.unsubscribe(); - this.outletDataSubscriptions.delete(outlet); - } - - private subscribeToRouteData(outlet: IonRouterOutlet) { - const { activatedRoute } = outlet; - const dataSubscription = combineLatest([activatedRoute.queryParams, activatedRoute.params, activatedRoute.data]) - .pipe( - switchMap(([queryParams, params, data], index) => { - data = { ...queryParams, ...params, ...data }; - // Get the first result from the data subscription synchronously so it's available to - // the component as soon as possible (and doesn't require a second change detection). - if (index === 0) { - return of(data); - } - // Promise.resolve is used to avoid synchronously writing the wrong data when - // two of the Observables in the `combineLatest` stream emit one after - // another. - return Promise.resolve(data); - }) - ) - .subscribe((data) => { - // Outlet may have been deactivated or changed names to be associated with a different - // route - if ( - !outlet.isActivated || - !outlet.activatedComponentRef || - outlet.activatedRoute !== activatedRoute || - activatedRoute.component === null - ) { - this.unsubscribeFromRouteData(outlet); - return; - } - - const mirror = reflectComponentType(activatedRoute.component); - if (!mirror) { - this.unsubscribeFromRouteData(outlet); - return; - } - - for (const { templateName } of mirror.inputs) { - outlet.activatedComponentRef.setInput(templateName, data[templateName]); - } - }); - - this.outletDataSubscriptions.set(outlet, dataSubscription); - } -} +export class IonRouterOutlet extends IonRouterOutletBase {} diff --git a/packages/angular/src/directives/navigation/ion-tabs.ts b/packages/angular/src/directives/navigation/ion-tabs.ts index 1fa8965218b..2d24e822eec 100644 --- a/packages/angular/src/directives/navigation/ion-tabs.ts +++ b/packages/angular/src/directives/navigation/ion-tabs.ts @@ -1,22 +1,9 @@ -import { - AfterContentChecked, - AfterContentInit, - Component, - ContentChild, - ContentChildren, - ElementRef, - EventEmitter, - HostListener, - Output, - QueryList, - ViewChild, -} from '@angular/core'; +import { Component, ContentChild, ContentChildren, ViewChild, QueryList } from '@angular/core'; +import { IonTabs as IonTabsBase } from '@ionic/angular/common'; -import { NavController } from '../../providers/nav-controller'; import { IonTabBar } from '../proxies'; import { IonRouterOutlet } from './ion-router-outlet'; -import { StackEvent } from './stack-utils'; @Component({ selector: 'ion-tabs', @@ -55,157 +42,9 @@ import { StackEvent } from './stack-utils'; ], }) // eslint-disable-next-line @angular-eslint/component-class-suffix -export class IonTabs implements AfterContentInit, AfterContentChecked { +export class IonTabs extends IonTabsBase { @ViewChild('outlet', { read: IonRouterOutlet, static: false }) outlet: IonRouterOutlet; - @ViewChild('tabsInner', { read: ElementRef, static: true }) tabsInner: ElementRef; @ContentChild(IonTabBar, { static: false }) tabBar: IonTabBar | undefined; @ContentChildren(IonTabBar) tabBars: QueryList; - - @Output() ionTabsWillChange = new EventEmitter<{ tab: string }>(); - @Output() ionTabsDidChange = new EventEmitter<{ tab: string }>(); - - private tabBarSlot = 'bottom'; - - constructor(private navCtrl: NavController) {} - - ngAfterContentInit(): void { - this.detectSlotChanges(); - } - - ngAfterContentChecked(): void { - this.detectSlotChanges(); - } - - /** - * @internal - */ - onPageSelected(detail: StackEvent): void { - const stackId = detail.enteringView.stackId; - if (detail.tabSwitch && stackId !== undefined) { - this.ionTabsWillChange.emit({ tab: stackId }); - if (this.tabBar) { - this.tabBar.selectedTab = stackId; - } - this.ionTabsDidChange.emit({ tab: stackId }); - } - } - - /** - * When a tab button is clicked, there are several scenarios: - * 1. If the selected tab is currently active (the tab button has been clicked - * again), then it should go to the root view for that tab. - * - * a. Get the saved root view from the router outlet. If the saved root view - * matches the tabRootUrl, set the route view to this view including the - * navigation extras. - * b. If the saved root view from the router outlet does - * not match, navigate to the tabRootUrl. No navigation extras are - * included. - * - * 2. If the current tab tab is not currently selected, get the last route - * view from the router outlet. - * - * a. If the last route view exists, navigate to that view including any - * navigation extras - * b. If the last route view doesn't exist, then navigate - * to the default tabRootUrl - */ - @HostListener('ionTabButtonClick', ['$event']) - select(tabOrEvent: string | CustomEvent): Promise | undefined { - const isTabString = typeof tabOrEvent === 'string'; - const tab = isTabString ? tabOrEvent : (tabOrEvent as CustomEvent).detail.tab; - const alreadySelected = this.outlet.getActiveStackId() === tab; - const tabRootUrl = `${this.outlet.tabsPrefix}/${tab}`; - - /** - * If this is a nested tab, prevent the event - * from bubbling otherwise the outer tabs - * will respond to this event too, causing - * the app to get directed to the wrong place. - */ - if (!isTabString) { - (tabOrEvent as CustomEvent).stopPropagation(); - } - - if (alreadySelected) { - const activeStackId = this.outlet.getActiveStackId(); - const activeView = this.outlet.getLastRouteView(activeStackId); - - // If on root tab, do not navigate to root tab again - if (activeView?.url === tabRootUrl) { - return; - } - - const rootView = this.outlet.getRootView(tab); - const navigationExtras = rootView && tabRootUrl === rootView.url && rootView.savedExtras; - return this.navCtrl.navigateRoot(tabRootUrl, { - ...navigationExtras, - animated: true, - animationDirection: 'back', - }); - } else { - const lastRoute = this.outlet.getLastRouteView(tab); - /** - * If there is a lastRoute, goto that, otherwise goto the fallback url of the - * selected tab - */ - const url = lastRoute?.url || tabRootUrl; - const navigationExtras = lastRoute?.savedExtras; - - return this.navCtrl.navigateRoot(url, { - ...navigationExtras, - animated: true, - animationDirection: 'back', - }); - } - } - - getSelected(): string | undefined { - return this.outlet.getActiveStackId(); - } - - /** - * Detects changes to the slot attribute of the tab bar. - * - * If the slot attribute has changed, then the tab bar - * should be relocated to the new slot position. - */ - private detectSlotChanges(): void { - this.tabBars.forEach((tabBar: any) => { - // el is a protected attribute from the generated component wrapper - const currentSlot = tabBar.el.getAttribute('slot'); - - if (currentSlot !== this.tabBarSlot) { - this.tabBarSlot = currentSlot; - this.relocateTabBar(); - } - }); - } - - /** - * Relocates the tab bar to the new slot position. - */ - private relocateTabBar(): void { - /** - * `el` is a protected attribute from the generated component wrapper. - * To avoid having to manually create the wrapper for tab bar, we - * cast the tab bar to any and access the protected attribute. - */ - const tabBar = (this.tabBar as any).el as HTMLElement; - - if (this.tabBarSlot === 'top') { - /** - * A tab bar with a slot of "top" should be inserted - * at the top of the container. - */ - this.tabsInner.nativeElement.before(tabBar); - } else { - /** - * A tab bar with a slot of "bottom" or without a slot - * should be inserted at the end of the container. - */ - this.tabsInner.nativeElement.after(tabBar); - } - } } diff --git a/packages/angular/src/directives/navigation/nav-delegate.ts b/packages/angular/src/directives/navigation/nav-delegate.ts index bdf7613b85e..000ccc060c6 100644 --- a/packages/angular/src/directives/navigation/nav-delegate.ts +++ b/packages/angular/src/directives/navigation/nav-delegate.ts @@ -1,40 +1,8 @@ -import { ElementRef, Injector, Directive, EnvironmentInjector } from '@angular/core'; -import { AngularDelegate } from '@ionic/angular/common'; +import { Directive } from '@angular/core'; +import { NavDelegate as NavDelegateBase } from '@ionic/angular/common'; -import { ProxyCmp, proxyOutputs } from '../angular-component-lib/utils'; - -@ProxyCmp({ - inputs: ['animated', 'animation', 'root', 'rootParams', 'swipeGesture'], - methods: [ - 'push', - 'insert', - 'insertPages', - 'pop', - 'popTo', - 'popToRoot', - 'removeIndex', - 'setRoot', - 'setPages', - 'getActive', - 'getByIndex', - 'canGoBack', - 'getPrevious', - ], -}) @Directive({ selector: 'ion-nav', }) // eslint-disable-next-line @angular-eslint/directive-class-suffix -export class NavDelegate { - protected el: HTMLElement; - constructor( - ref: ElementRef, - environmentInjector: EnvironmentInjector, - injector: Injector, - angularDelegate: AngularDelegate - ) { - this.el = ref.nativeElement; - ref.nativeElement.delegate = angularDelegate.create(environmentInjector, injector); - proxyOutputs(this, this.el, ['ionNavDidChange', 'ionNavWillChange']); - } -} +export class NavDelegate extends NavDelegateBase {} diff --git a/packages/angular/src/directives/navigation/router-link-delegate.ts b/packages/angular/src/directives/navigation/router-link-delegate.ts index 40a2a395a8b..066fd86b524 100644 --- a/packages/angular/src/directives/navigation/router-link-delegate.ts +++ b/packages/angular/src/directives/navigation/router-link-delegate.ts @@ -1,9 +1,8 @@ -import { LocationStrategy } from '@angular/common'; -import { ElementRef, OnChanges, OnInit, Directive, HostListener, Input, Optional } from '@angular/core'; -import { Router, RouterLink } from '@angular/router'; -import { AnimationBuilder, RouterDirection } from '@ionic/core'; - -import { NavController } from '../../providers/nav-controller'; +import { Directive } from '@angular/core'; +import { + RouterLinkDelegateDirective as RouterLinkDelegateBase, + RouterLinkWithHrefDelegateDirective as RouterLinkHrefDelegateBase, +} from '@ionic/angular/common'; /** * Adds support for Ionic routing directions and animations to the base Angular router link directive. @@ -14,93 +13,9 @@ import { NavController } from '../../providers/nav-controller'; @Directive({ selector: ':not(a):not(area)[routerLink]', }) -export class RouterLinkDelegateDirective implements OnInit, OnChanges { - @Input() - routerDirection: RouterDirection = 'forward'; - - @Input() - routerAnimation?: AnimationBuilder; - - constructor( - private locationStrategy: LocationStrategy, - private navCtrl: NavController, - private elementRef: ElementRef, - private router: Router, - @Optional() private routerLink?: RouterLink - ) {} - - ngOnInit(): void { - this.updateTargetUrlAndHref(); - } - - ngOnChanges(): void { - this.updateTargetUrlAndHref(); - } - - private updateTargetUrlAndHref() { - if (this.routerLink?.urlTree) { - const href = this.locationStrategy.prepareExternalUrl(this.router.serializeUrl(this.routerLink.urlTree)); - this.elementRef.nativeElement.href = href; - } - } - - /** - * @internal - */ - @HostListener('click', ['$event']) - onClick(ev: UIEvent): void { - this.navCtrl.setDirection(this.routerDirection, undefined, undefined, this.routerAnimation); - - /** - * This prevents the browser from - * performing a page reload when pressing - * an Ionic component with routerLink. - * The page reload interferes with routing - * and causes ion-back-button to disappear - * since the local history is wiped on reload. - */ - ev.preventDefault(); - } -} +export class RouterLinkDelegateDirective extends RouterLinkDelegateBase {} @Directive({ selector: 'a[routerLink],area[routerLink]', }) -export class RouterLinkWithHrefDelegateDirective implements OnInit, OnChanges { - @Input() - routerDirection: RouterDirection = 'forward'; - - @Input() - routerAnimation?: AnimationBuilder; - - constructor( - private locationStrategy: LocationStrategy, - private navCtrl: NavController, - private elementRef: ElementRef, - private router: Router, - @Optional() private routerLink?: RouterLink - ) {} - - ngOnInit(): void { - this.updateTargetUrlAndHref(); - } - - ngOnChanges(): void { - this.updateTargetUrlAndHref(); - } - - private updateTargetUrlAndHref() { - if (this.routerLink?.urlTree) { - const href = this.locationStrategy.prepareExternalUrl(this.router.serializeUrl(this.routerLink.urlTree)); - this.elementRef.nativeElement.href = href; - } - } - - /** - * @internal - */ - @HostListener('click') - onClick(): void { - this.navCtrl.setDirection(this.routerDirection, undefined, undefined, this.routerAnimation); - } -} +export class RouterLinkWithHrefDelegateDirective extends RouterLinkHrefDelegateBase {} diff --git a/packages/angular/src/index.ts b/packages/angular/src/index.ts index 77860a8daa2..ee3d20c6515 100644 --- a/packages/angular/src/index.ts +++ b/packages/angular/src/index.ts @@ -30,12 +30,12 @@ export { AnimationController, GestureController, DomController, + NavController, Config, Platform, AngularDelegate, NavParams, } from '@ionic/angular/common'; -export { NavController } from './providers/nav-controller'; // ROUTER STRATEGY export { IonicRouteStrategy } from './util/ionic-router-reuse-strategy'; diff --git a/packages/angular/src/ionic-module.ts b/packages/angular/src/ionic-module.ts index b30fb8b97d0..d24c4b727ac 100644 --- a/packages/angular/src/ionic-module.ts +++ b/packages/angular/src/ionic-module.ts @@ -1,7 +1,14 @@ import { CommonModule, DOCUMENT } from '@angular/common'; import { ModuleWithProviders, APP_INITIALIZER, NgModule, NgZone } from '@angular/core'; import { Router } from '@angular/router'; -import { ModalController, PopoverController, ConfigToken, AngularDelegate } from '@ionic/angular/common'; +import { + ModalController, + PopoverController, + ConfigToken, + AngularDelegate, + INPUT_BINDER, + RoutedComponentInputBinder, +} from '@ionic/angular/common'; import { IonicConfig } from '@ionic/core'; import { appInitialize } from './app-initialize'; @@ -13,7 +20,7 @@ import { TextValueAccessorDirective, } from './directives/control-value-accessors'; import { IonBackButtonDelegateDirective } from './directives/navigation/ion-back-button'; -import { INPUT_BINDER, IonRouterOutlet, RoutedComponentInputBinder } from './directives/navigation/ion-router-outlet'; +import { IonRouterOutlet } from './directives/navigation/ion-router-outlet'; import { IonTabs } from './directives/navigation/ion-tabs'; import { NavDelegate } from './directives/navigation/nav-delegate'; import { From 3ea7488b472cad05ed33367cd0e6114b4a3783c1 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Fri, 4 Aug 2023 14:57:32 -0400 Subject: [PATCH 06/24] feat(angular): add standalone router outlet (#27926) --- packages/angular/common/src/index.ts | 2 ++ packages/angular/standalone/src/index.ts | 4 +--- .../standalone/src/navigation/router-outlet.ts | 13 +++++++++++++ .../base/e2e/src/standalone/router-outlet.spec.ts | 9 +++++++++ .../standalone/app-standalone/app.component.html | 2 +- .../app/standalone/app-standalone/app.component.ts | 4 ++-- .../src/app/standalone/app-standalone/app.routes.ts | 1 + .../router-outlet/router-outlet.component.html | 1 + .../router-outlet/router-outlet.component.ts | 8 ++++++++ 9 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 packages/angular/standalone/src/navigation/router-outlet.ts create mode 100644 packages/angular/test/base/e2e/src/standalone/router-outlet.spec.ts create mode 100644 packages/angular/test/base/src/app/standalone/router-outlet/router-outlet.component.html create mode 100644 packages/angular/test/base/src/app/standalone/router-outlet/router-outlet.component.ts diff --git a/packages/angular/common/src/index.ts b/packages/angular/common/src/index.ts index 74bc82f3c80..7229a93a0d1 100644 --- a/packages/angular/common/src/index.ts +++ b/packages/angular/common/src/index.ts @@ -30,3 +30,5 @@ export { } from './directives/navigation/router-link-delegate'; export { NavDelegate } from './directives/navigation/nav-delegate'; export { IonTabs } from './directives/navigation/tabs'; + +export { ProxyCmp } from './utils/proxy'; diff --git a/packages/angular/standalone/src/index.ts b/packages/angular/standalone/src/index.ts index a5a128b2514..f5fc4e8273d 100644 --- a/packages/angular/standalone/src/index.ts +++ b/packages/angular/standalone/src/index.ts @@ -1,3 +1 @@ -// This is required to get ng-packagr to build. -// Remove this when you actually have something to export -export const placeholder = true; +export { IonRouterOutlet } from './navigation/router-outlet'; diff --git a/packages/angular/standalone/src/navigation/router-outlet.ts b/packages/angular/standalone/src/navigation/router-outlet.ts new file mode 100644 index 00000000000..2b750736e06 --- /dev/null +++ b/packages/angular/standalone/src/navigation/router-outlet.ts @@ -0,0 +1,13 @@ +import { Directive } from '@angular/core'; +import { IonRouterOutlet as IonRouterOutletBase, ProxyCmp } from '@ionic/angular/common'; +import { defineCustomElement } from '@ionic/core/components/ion-router-outlet.js'; + +@ProxyCmp({ + defineCustomElementFn: defineCustomElement, +}) +@Directive({ + selector: 'ion-router-outlet', + standalone: true, +}) +// eslint-disable-next-line @angular-eslint/directive-class-suffix +export class IonRouterOutlet extends IonRouterOutletBase {} diff --git a/packages/angular/test/base/e2e/src/standalone/router-outlet.spec.ts b/packages/angular/test/base/e2e/src/standalone/router-outlet.spec.ts new file mode 100644 index 00000000000..37beac46fc8 --- /dev/null +++ b/packages/angular/test/base/e2e/src/standalone/router-outlet.spec.ts @@ -0,0 +1,9 @@ +describe('Router Outlet', () => { + beforeEach(() => { + cy.visit('/standalone/router-outlet'); + }) + + it('should render a as a child page of the router outlet', () => { + cy.ionPageVisible('ion-router-outlet app-router-outlet'); + }); +}) diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.component.html b/packages/angular/test/base/src/app/standalone/app-standalone/app.component.html index 0680b43f9c6..15a92a22447 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.component.html +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.component.html @@ -1 +1 @@ - + diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.component.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.component.ts index b8ba6d5caa7..280de0d8c76 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.component.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { RouterModule } from '@angular/router'; - +import { IonRouterOutlet } from '@ionic/angular/standalone'; /** * This temporary code initialized Ionic and ensures components are visible. * TODO FW-4766 Can be removed when ticket is implemented @@ -15,7 +15,7 @@ document.querySelector('html')!.classList.add('ion-ce') selector: 'app-root-standalone', templateUrl: './app.component.html', standalone: true, - imports: [RouterModule] + imports: [RouterModule, IonRouterOutlet] }) export class AppComponent { } diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts index 3182d3167a2..a453e1e46e3 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts @@ -7,6 +7,7 @@ export const routes: Routes = [ component: AppComponent, children: [ { path: 'test', loadComponent: () => import('../test/test.component').then(m => m.TestComponent) }, + { path: 'router-outlet', loadComponent: () => import('../router-outlet/router-outlet.component').then(m => m.RouterOutletComponent) }, ] }, ]; diff --git a/packages/angular/test/base/src/app/standalone/router-outlet/router-outlet.component.html b/packages/angular/test/base/src/app/standalone/router-outlet/router-outlet.component.html new file mode 100644 index 00000000000..83a1ae37758 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/router-outlet/router-outlet.component.html @@ -0,0 +1 @@ +This should be visible and rendered inside of an ion-router-outlet. diff --git a/packages/angular/test/base/src/app/standalone/router-outlet/router-outlet.component.ts b/packages/angular/test/base/src/app/standalone/router-outlet/router-outlet.component.ts new file mode 100644 index 00000000000..c28ea0e309d --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/router-outlet/router-outlet.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-router-outlet', + templateUrl: './router-outlet.component.html', + standalone: true +}) +export class RouterOutletComponent {} From 9f20780d66a113410e5450139f8ecab3a9d4a0c9 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Fri, 4 Aug 2023 15:12:11 -0400 Subject: [PATCH 07/24] feat(angular): add standalone back-button (#27927) --- .../src/directives/navigation/back-button.ts | 22 ++++++++++--- packages/angular/common/src/index.ts | 2 +- .../directives/navigation/ion-back-button.ts | 16 +++++++--- packages/angular/standalone/src/index.ts | 1 + .../standalone/src/navigation/back-button.ts | 32 +++++++++++++++++++ .../e2e/src/standalone/back-button.spec.ts | 14 ++++++++ .../standalone/app-standalone/app.routes.ts | 1 + .../back-button/back-button.component.html | 1 + .../back-button/back-button.component.ts | 10 ++++++ 9 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 packages/angular/standalone/src/navigation/back-button.ts create mode 100644 packages/angular/test/base/e2e/src/standalone/back-button.spec.ts create mode 100644 packages/angular/test/base/src/app/standalone/back-button/back-button.component.html create mode 100644 packages/angular/test/base/src/app/standalone/back-button/back-button.component.ts diff --git a/packages/angular/common/src/directives/navigation/back-button.ts b/packages/angular/common/src/directives/navigation/back-button.ts index 8ddc9e14833..7ec7136a8c4 100644 --- a/packages/angular/common/src/directives/navigation/back-button.ts +++ b/packages/angular/common/src/directives/navigation/back-button.ts @@ -1,27 +1,41 @@ -import { Directive, HostListener, Input, Optional } from '@angular/core'; +import { Directive, HostListener, Input, Optional, ElementRef, NgZone } from '@angular/core'; import type { AnimationBuilder } from '@ionic/core/components'; import { Config } from '../../providers/config'; import { NavController } from '../../providers/nav-controller'; +import { ProxyCmp } from '../../utils/proxy'; import { IonRouterOutlet } from './router-outlet'; +const BACK_BUTTON_INPUTS = ['color', 'defaultHref', 'disabled', 'icon', 'mode', 'routerAnimation', 'text', 'type']; + +@ProxyCmp({ + inputs: BACK_BUTTON_INPUTS, +}) @Directive({ selector: 'ion-back-button', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: BACK_BUTTON_INPUTS, }) // eslint-disable-next-line @angular-eslint/directive-class-suffix -export class IonBackButton { +export class IonBackButtonDelegate { @Input() defaultHref: string | undefined | null; @Input() routerAnimation?: AnimationBuilder; + protected el: HTMLElement; + constructor( @Optional() private routerOutlet: IonRouterOutlet, private navCtrl: NavController, - private config: Config - ) {} + private config: Config, + private r: ElementRef, + protected z: NgZone + ) { + this.el = this.r.nativeElement; + } /** * @internal diff --git a/packages/angular/common/src/index.ts b/packages/angular/common/src/index.ts index 7229a93a0d1..f3439baeb56 100644 --- a/packages/angular/common/src/index.ts +++ b/packages/angular/common/src/index.ts @@ -23,7 +23,7 @@ export { NavParams } from './directives/navigation/nav-params'; export { IonRouterOutlet, INPUT_BINDER, RoutedComponentInputBinder } from './directives/navigation/router-outlet'; export type { StackEvent } from './directives/navigation/stack-utils'; -export { IonBackButton } from './directives/navigation/back-button'; +export { IonBackButtonDelegate } from './directives/navigation/back-button'; export { RouterLinkDelegateDirective, RouterLinkWithHrefDelegateDirective, diff --git a/packages/angular/src/directives/navigation/ion-back-button.ts b/packages/angular/src/directives/navigation/ion-back-button.ts index fbf14d7fb5a..309a5cf53c5 100644 --- a/packages/angular/src/directives/navigation/ion-back-button.ts +++ b/packages/angular/src/directives/navigation/ion-back-button.ts @@ -1,5 +1,5 @@ -import { Directive, Optional } from '@angular/core'; -import { IonBackButton as IonBackButtonBase, NavController, Config } from '@ionic/angular/common'; +import { Directive, Optional, ElementRef, NgZone } from '@angular/core'; +import { IonBackButtonDelegate as IonBackButtonDelegateBase, NavController, Config } from '@ionic/angular/common'; import { IonRouterOutlet } from './ion-router-outlet'; @@ -7,8 +7,14 @@ import { IonRouterOutlet } from './ion-router-outlet'; selector: 'ion-back-button', }) // eslint-disable-next-line @angular-eslint/directive-class-suffix -export class IonBackButtonDelegateDirective extends IonBackButtonBase { - constructor(@Optional() routerOutlet: IonRouterOutlet, navCtrl: NavController, config: Config) { - super(routerOutlet, navCtrl, config); +export class IonBackButtonDelegateDirective extends IonBackButtonDelegateBase { + constructor( + @Optional() routerOutlet: IonRouterOutlet, + navCtrl: NavController, + config: Config, + r: ElementRef, + z: NgZone + ) { + super(routerOutlet, navCtrl, config, r, z); } } diff --git a/packages/angular/standalone/src/index.ts b/packages/angular/standalone/src/index.ts index f5fc4e8273d..9dcb40d1f94 100644 --- a/packages/angular/standalone/src/index.ts +++ b/packages/angular/standalone/src/index.ts @@ -1 +1,2 @@ +export { IonBackButton } from './navigation/back-button'; export { IonRouterOutlet } from './navigation/router-outlet'; diff --git a/packages/angular/standalone/src/navigation/back-button.ts b/packages/angular/standalone/src/navigation/back-button.ts new file mode 100644 index 00000000000..d9e98711623 --- /dev/null +++ b/packages/angular/standalone/src/navigation/back-button.ts @@ -0,0 +1,32 @@ +import { Component, Optional, ChangeDetectionStrategy, ElementRef, NgZone } from '@angular/core'; +import { + IonBackButtonDelegate as IonBackButtonDelegateBase, + NavController, + Config, + ProxyCmp, +} from '@ionic/angular/common'; +import { defineCustomElement } from '@ionic/core/components/ion-back-button.js'; + +import { IonRouterOutlet } from './router-outlet'; + +@ProxyCmp({ + defineCustomElementFn: defineCustomElement, +}) +@Component({ + selector: 'ion-back-button', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + standalone: true, +}) +// eslint-disable-next-line @angular-eslint/directive-class-suffix +export class IonBackButton extends IonBackButtonDelegateBase { + constructor( + @Optional() routerOutlet: IonRouterOutlet, + navCtrl: NavController, + config: Config, + r: ElementRef, + z: NgZone + ) { + super(routerOutlet, navCtrl, config, r, z); + } +} diff --git a/packages/angular/test/base/e2e/src/standalone/back-button.spec.ts b/packages/angular/test/base/e2e/src/standalone/back-button.spec.ts new file mode 100644 index 00000000000..7030dcdf5e5 --- /dev/null +++ b/packages/angular/test/base/e2e/src/standalone/back-button.spec.ts @@ -0,0 +1,14 @@ +describe('Back Button', () => { + beforeEach(() => { + cy.visit('/standalone/back-button'); + }) + + it('should be visible and navigate back to page', () => { + cy.ionPageVisible('app-back-button'); + + cy.get('ion-back-button').click(); + + cy.ionPageDoesNotExist('app-back-button'); + cy.ionPageVisible('app-router-outlet'); + }); +}) diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts index a453e1e46e3..a045a0a69ae 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts @@ -8,6 +8,7 @@ export const routes: Routes = [ children: [ { path: 'test', loadComponent: () => import('../test/test.component').then(m => m.TestComponent) }, { path: 'router-outlet', loadComponent: () => import('../router-outlet/router-outlet.component').then(m => m.RouterOutletComponent) }, + { path: 'back-button', loadComponent: () => import('../back-button/back-button.component').then(m => m.BackButtonComponent) }, ] }, ]; diff --git a/packages/angular/test/base/src/app/standalone/back-button/back-button.component.html b/packages/angular/test/base/src/app/standalone/back-button/back-button.component.html new file mode 100644 index 00000000000..0224eb0fb0a --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/back-button/back-button.component.html @@ -0,0 +1 @@ + diff --git a/packages/angular/test/base/src/app/standalone/back-button/back-button.component.ts b/packages/angular/test/base/src/app/standalone/back-button/back-button.component.ts new file mode 100644 index 00000000000..7f9280d1d71 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/back-button/back-button.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; +import { IonBackButton } from '@ionic/angular/standalone'; + +@Component({ + selector: 'app-back-button', + templateUrl: './back-button.component.html', + standalone: true, + imports: [IonBackButton] +}) +export class BackButtonComponent {} From 37acdf971160fcff5513f97c0212d9897f73b5b8 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Fri, 4 Aug 2023 15:45:50 -0400 Subject: [PATCH 08/24] feat(angular): add standalone modal (#27885) --- packages/angular/common/src/index.ts | 3 + packages/angular/common/src/overlays/modal.ts | 133 +++++++++++++++++ .../angular/src/directives/overlays/modal.ts | 134 +----------------- packages/angular/standalone/src/index.ts | 1 + .../angular/standalone/src/overlays/modal.ts | 18 +++ .../base/e2e/src/standalone/modal.spec.ts | 12 ++ .../standalone/app-standalone/app.routes.ts | 1 + .../app/standalone/modal/modal.component.html | 7 + .../app/standalone/modal/modal.component.ts | 11 ++ 9 files changed, 189 insertions(+), 131 deletions(-) create mode 100644 packages/angular/common/src/overlays/modal.ts create mode 100644 packages/angular/standalone/src/overlays/modal.ts create mode 100644 packages/angular/test/base/e2e/src/standalone/modal.spec.ts create mode 100644 packages/angular/test/base/src/app/standalone/modal/modal.component.html create mode 100644 packages/angular/test/base/src/app/standalone/modal/modal.component.ts diff --git a/packages/angular/common/src/index.ts b/packages/angular/common/src/index.ts index f3439baeb56..2c689cc6d6f 100644 --- a/packages/angular/common/src/index.ts +++ b/packages/angular/common/src/index.ts @@ -20,6 +20,9 @@ export { bindLifecycleEvents, AngularDelegate } from './providers/angular-delega export type { IonicWindow } from './types/interfaces'; export { NavParams } from './directives/navigation/nav-params'; + +export { IonModal } from './overlays/modal'; + export { IonRouterOutlet, INPUT_BINDER, RoutedComponentInputBinder } from './directives/navigation/router-outlet'; export type { StackEvent } from './directives/navigation/stack-utils'; diff --git a/packages/angular/common/src/overlays/modal.ts b/packages/angular/common/src/overlays/modal.ts new file mode 100644 index 00000000000..0ad1fd49c02 --- /dev/null +++ b/packages/angular/common/src/overlays/modal.ts @@ -0,0 +1,133 @@ +import { + ChangeDetectorRef, + ContentChild, + Directive, + ElementRef, + EventEmitter, + NgZone, + TemplateRef, +} from '@angular/core'; +import { Components, ModalBreakpointChangeEventDetail } from '@ionic/core'; + +import { ProxyCmp, proxyOutputs } from '../utils/proxy'; + +export declare interface IonModal extends Components.IonModal { + /** + * Emitted after the modal has presented. + **/ + ionModalDidPresent: EventEmitter; + /** + * Emitted before the modal has presented. + */ + ionModalWillPresent: EventEmitter; + /** + * Emitted before the modal has dismissed. + */ + ionModalWillDismiss: EventEmitter; + /** + * Emitted after the modal has dismissed. + */ + ionModalDidDismiss: EventEmitter; + /** + * Emitted after the modal breakpoint has changed. + */ + ionBreakpointDidChange: EventEmitter>; + /** + * Emitted after the modal has presented. Shorthand for ionModalDidPresent. + */ + didPresent: EventEmitter; + /** + * Emitted before the modal has presented. Shorthand for ionModalWillPresent. + */ + willPresent: EventEmitter; + /** + * Emitted before the modal has dismissed. Shorthand for ionModalWillDismiss. + */ + willDismiss: EventEmitter; + /** + * Emitted after the modal has dismissed. Shorthand for ionModalDidDismiss. + */ + didDismiss: EventEmitter; +} + +const MODAL_INPUTS = [ + 'animated', + 'keepContentsMounted', + 'backdropBreakpoint', + 'backdropDismiss', + 'breakpoints', + 'canDismiss', + 'cssClass', + 'enterAnimation', + 'event', + 'handle', + 'handleBehavior', + 'initialBreakpoint', + 'isOpen', + 'keyboardClose', + 'leaveAnimation', + 'mode', + 'presentingElement', + 'showBackdrop', + 'translucent', + 'trigger', +]; + +const MODAL_METHODS = [ + 'present', + 'dismiss', + 'onDidDismiss', + 'onWillDismiss', + 'setCurrentBreakpoint', + 'getCurrentBreakpoint', +]; + +@ProxyCmp({ + inputs: MODAL_INPUTS, + methods: MODAL_METHODS, +}) +/** + * @Component extends from @Directive + * so by defining the inputs here we + * do not need to re-define them for the + * lazy loaded popover. + */ +@Directive({ + selector: 'ion-modal', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: MODAL_INPUTS, +}) + +// eslint-disable-next-line @angular-eslint/directive-class-suffix +export class IonModal { + // TODO(FW-2827): type + @ContentChild(TemplateRef, { static: false }) template: TemplateRef; + + isCmpOpen = false; + + protected el: HTMLElement; + + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + this.el = r.nativeElement; + + this.el.addEventListener('ionMount', () => { + this.isCmpOpen = true; + c.detectChanges(); + }); + this.el.addEventListener('didDismiss', () => { + this.isCmpOpen = false; + c.detectChanges(); + }); + proxyOutputs(this, this.el, [ + 'ionModalDidPresent', + 'ionModalWillPresent', + 'ionModalWillDismiss', + 'ionModalDidDismiss', + 'ionBreakpointDidChange', + 'didPresent', + 'willPresent', + 'willDismiss', + 'didDismiss', + ]); + } +} diff --git a/packages/angular/src/directives/overlays/modal.ts b/packages/angular/src/directives/overlays/modal.ts index 645a542c579..9f9a22d9d6c 100644 --- a/packages/angular/src/directives/overlays/modal.ts +++ b/packages/angular/src/directives/overlays/modal.ts @@ -1,139 +1,11 @@ -/* eslint-disable */ -/* tslint:disable */ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ContentChild, - ElementRef, - EventEmitter, - NgZone, - TemplateRef, -} from '@angular/core'; -import { ProxyCmp, proxyOutputs } from '../angular-component-lib/utils'; -import { Components, ModalBreakpointChangeEventDetail } from '@ionic/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { IonModal as IonModalBase } from '@ionic/angular/common'; -export declare interface IonModal extends Components.IonModal { - /** - * Emitted after the modal has presented. - **/ - ionModalDidPresent: EventEmitter; - /** - * Emitted before the modal has presented. - */ - ionModalWillPresent: EventEmitter; - /** - * Emitted before the modal has dismissed. - */ - ionModalWillDismiss: EventEmitter; - /** - * Emitted after the modal has dismissed. - */ - ionModalDidDismiss: EventEmitter; - /** - * Emitted after the modal breakpoint has changed. - */ - ionBreakpointDidChange: EventEmitter>; - /** - * Emitted after the modal has presented. Shorthand for ionModalWillDismiss. - */ - didPresent: EventEmitter; - /** - * Emitted before the modal has presented. Shorthand for ionModalWillPresent. - */ - willPresent: EventEmitter; - /** - * Emitted before the modal has dismissed. Shorthand for ionModalWillDismiss. - */ - willDismiss: EventEmitter; - /** - * Emitted after the modal has dismissed. Shorthand for ionModalDidDismiss. - */ - didDismiss: EventEmitter; -} -@ProxyCmp({ - inputs: [ - 'animated', - 'keepContentsMounted', - 'backdropBreakpoint', - 'backdropDismiss', - 'breakpoints', - 'canDismiss', - 'cssClass', - 'enterAnimation', - 'event', - 'handle', - 'handleBehavior', - 'initialBreakpoint', - 'isOpen', - 'keyboardClose', - 'leaveAnimation', - 'mode', - 'presentingElement', - 'showBackdrop', - 'translucent', - 'trigger', - ], - methods: ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss', 'setCurrentBreakpoint', 'getCurrentBreakpoint'], -}) @Component({ selector: 'ion-modal', changeDetection: ChangeDetectionStrategy.OnPush, template: `
`, - inputs: [ - 'animated', - 'keepContentsMounted', - 'backdropBreakpoint', - 'backdropDismiss', - 'breakpoints', - 'canDismiss', - 'cssClass', - 'enterAnimation', - 'event', - 'handle', - 'handleBehavior', - 'initialBreakpoint', - 'isOpen', - 'keyboardClose', - 'leaveAnimation', - 'mode', - 'presentingElement', - 'showBackdrop', - 'translucent', - 'trigger', - ], }) -export class IonModal { - // TODO(FW-2827): type - @ContentChild(TemplateRef, { static: false }) template: TemplateRef; - - isCmpOpen: boolean = false; - - protected el: HTMLElement; - - constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { - this.el = r.nativeElement; - - this.el.addEventListener('ionMount', () => { - this.isCmpOpen = true; - c.detectChanges(); - }); - this.el.addEventListener('didDismiss', () => { - this.isCmpOpen = false; - c.detectChanges(); - }); - proxyOutputs(this, this.el, [ - 'ionModalDidPresent', - 'ionModalWillPresent', - 'ionModalWillDismiss', - 'ionModalDidDismiss', - 'ionBreakpointDidChange', - 'didPresent', - 'willPresent', - 'willDismiss', - 'didDismiss', - ]); - } -} +export class IonModal extends IonModalBase {} diff --git a/packages/angular/standalone/src/index.ts b/packages/angular/standalone/src/index.ts index 9dcb40d1f94..b8ddcc657e6 100644 --- a/packages/angular/standalone/src/index.ts +++ b/packages/angular/standalone/src/index.ts @@ -1,2 +1,3 @@ export { IonBackButton } from './navigation/back-button'; +export { IonModal } from './overlays/modal'; export { IonRouterOutlet } from './navigation/router-outlet'; diff --git a/packages/angular/standalone/src/overlays/modal.ts b/packages/angular/standalone/src/overlays/modal.ts new file mode 100644 index 00000000000..6e132b2fb43 --- /dev/null +++ b/packages/angular/standalone/src/overlays/modal.ts @@ -0,0 +1,18 @@ +import { CommonModule } from '@angular/common'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { IonModal as IonModalBase, ProxyCmp } from '@ionic/angular/common'; +import { defineCustomElement } from '@ionic/core/components/ion-modal.js'; + +@ProxyCmp({ + defineCustomElementFn: defineCustomElement, +}) +@Component({ + selector: 'ion-modal', + changeDetection: ChangeDetectionStrategy.OnPush, + template: `
+ +
`, + standalone: true, + imports: [CommonModule], +}) +export class IonModal extends IonModalBase {} diff --git a/packages/angular/test/base/e2e/src/standalone/modal.spec.ts b/packages/angular/test/base/e2e/src/standalone/modal.spec.ts new file mode 100644 index 00000000000..ffa4df9dac7 --- /dev/null +++ b/packages/angular/test/base/e2e/src/standalone/modal.spec.ts @@ -0,0 +1,12 @@ +describe('Modals: Inline', () => { + beforeEach(() => { + cy.visit('/standalone/modal'); + }); + + it('should render modal', () => { + cy.get('button#open-modal').click(); + + cy.get('ion-modal').should('be.visible'); + cy.get('ion-modal #modal-content').should('exist'); + }); +}); diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts index a045a0a69ae..c7233f2a3ed 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts @@ -7,6 +7,7 @@ export const routes: Routes = [ component: AppComponent, children: [ { path: 'test', loadComponent: () => import('../test/test.component').then(m => m.TestComponent) }, + { path: 'modal', loadComponent: () => import('../modal/modal.component').then(m => m.ModalComponent) }, { path: 'router-outlet', loadComponent: () => import('../router-outlet/router-outlet.component').then(m => m.RouterOutletComponent) }, { path: 'back-button', loadComponent: () => import('../back-button/back-button.component').then(m => m.BackButtonComponent) }, ] diff --git a/packages/angular/test/base/src/app/standalone/modal/modal.component.html b/packages/angular/test/base/src/app/standalone/modal/modal.component.html new file mode 100644 index 00000000000..af65317ff2a --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/modal/modal.component.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/angular/test/base/src/app/standalone/modal/modal.component.ts b/packages/angular/test/base/src/app/standalone/modal/modal.component.ts new file mode 100644 index 00000000000..1dedd575387 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/modal/modal.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; +import { IonModal } from '@ionic/angular/standalone'; + +@Component({ + selector: 'app-test', + templateUrl: './modal.component.html', + standalone: true, + imports: [IonModal] +}) +export class ModalComponent { +} From 8a97e406a0788a0fe971974158975ace8d7c7c53 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Fri, 4 Aug 2023 16:55:46 -0400 Subject: [PATCH 09/24] feat(angular): add standalone popover (#27883) --- packages/angular/common/src/index.ts | 1 + .../angular/common/src/overlays/popover.ts | 121 ++++++++++++++++ .../src/directives/overlays/popover.ts | 130 +----------------- packages/angular/standalone/src/index.ts | 1 + .../standalone/src/overlays/popover.ts | 16 +++ .../base/e2e/src/standalone/popover.spec.ts | 12 ++ .../standalone/app-standalone/app.routes.ts | 2 +- .../standalone/popover/popover.component.html | 7 + .../standalone/popover/popover.component.ts | 11 ++ .../app/standalone/test/test.component.html | 1 - .../src/app/standalone/test/test.component.ts | 12 -- 11 files changed, 174 insertions(+), 140 deletions(-) create mode 100644 packages/angular/common/src/overlays/popover.ts create mode 100644 packages/angular/standalone/src/overlays/popover.ts create mode 100644 packages/angular/test/base/e2e/src/standalone/popover.spec.ts create mode 100644 packages/angular/test/base/src/app/standalone/popover/popover.component.html create mode 100644 packages/angular/test/base/src/app/standalone/popover/popover.component.ts delete mode 100644 packages/angular/test/base/src/app/standalone/test/test.component.html delete mode 100644 packages/angular/test/base/src/app/standalone/test/test.component.ts diff --git a/packages/angular/common/src/index.ts b/packages/angular/common/src/index.ts index 2c689cc6d6f..8cd0c975b42 100644 --- a/packages/angular/common/src/index.ts +++ b/packages/angular/common/src/index.ts @@ -21,6 +21,7 @@ export type { IonicWindow } from './types/interfaces'; export { NavParams } from './directives/navigation/nav-params'; +export { IonPopover } from './overlays/popover'; export { IonModal } from './overlays/modal'; export { IonRouterOutlet, INPUT_BINDER, RoutedComponentInputBinder } from './directives/navigation/router-outlet'; diff --git a/packages/angular/common/src/overlays/popover.ts b/packages/angular/common/src/overlays/popover.ts new file mode 100644 index 00000000000..383eb75f15b --- /dev/null +++ b/packages/angular/common/src/overlays/popover.ts @@ -0,0 +1,121 @@ +import { + ChangeDetectorRef, + ContentChild, + Directive, + ElementRef, + EventEmitter, + NgZone, + TemplateRef, +} from '@angular/core'; +import { Components } from '@ionic/core'; + +import { ProxyCmp, proxyOutputs } from '../utils/proxy'; + +export declare interface IonPopover extends Components.IonPopover { + /** + * Emitted after the popover has presented. + */ + ionPopoverDidPresent: EventEmitter; + /** + * Emitted before the popover has presented. + */ + ionPopoverWillPresent: EventEmitter; + /** + * Emitted after the popover has dismissed. + */ + ionPopoverWillDismiss: EventEmitter; + /** + * Emitted after the popover has dismissed. + */ + ionPopoverDidDismiss: EventEmitter; + /** + * Emitted after the popover has presented. Shorthand for ionPopoverDidPresent. + */ + didPresent: EventEmitter; + /** + * Emitted before the popover has presented. Shorthand for ionPopoverWillPresent. + */ + willPresent: EventEmitter; + /** + * Emitted after the popover has presented. Shorthand for ionPopoverWillDismiss. + */ + willDismiss: EventEmitter; + /** + * Emitted after the popover has dismissed. Shorthand for ionPopoverDidDismiss. + */ + didDismiss: EventEmitter; +} + +const POPOVER_INPUTS = [ + 'alignment', + 'animated', + 'arrow', + 'keepContentsMounted', + 'backdropDismiss', + 'cssClass', + 'dismissOnSelect', + 'enterAnimation', + 'event', + 'isOpen', + 'keyboardClose', + 'leaveAnimation', + 'mode', + 'showBackdrop', + 'translucent', + 'trigger', + 'triggerAction', + 'reference', + 'size', + 'side', +]; + +const POPOVER_METHODS = ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss']; + +@ProxyCmp({ + inputs: POPOVER_INPUTS, + methods: POPOVER_METHODS, +}) +/** + * @Component extends from @Directive + * so by defining the inputs here we + * do not need to re-define them for the + * lazy loaded popover. + */ +@Directive({ + selector: 'ion-popover', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: POPOVER_INPUTS, +}) + +// eslint-disable-next-line @angular-eslint/directive-class-suffix +export class IonPopover { + // TODO(FW-2827): type + @ContentChild(TemplateRef, { static: false }) template: TemplateRef; + + isCmpOpen = false; + + protected el: HTMLElement; + + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + this.el = r.nativeElement; + + this.el.addEventListener('ionMount', () => { + this.isCmpOpen = true; + c.detectChanges(); + }); + this.el.addEventListener('didDismiss', () => { + this.isCmpOpen = false; + c.detectChanges(); + }); + proxyOutputs(this, this.el, [ + 'ionPopoverDidPresent', + 'ionPopoverWillPresent', + 'ionPopoverWillDismiss', + 'ionPopoverDidDismiss', + 'didPresent', + 'willPresent', + 'willDismiss', + 'didDismiss', + ]); + } +} diff --git a/packages/angular/src/directives/overlays/popover.ts b/packages/angular/src/directives/overlays/popover.ts index a1c1b372397..05a0580773b 100644 --- a/packages/angular/src/directives/overlays/popover.ts +++ b/packages/angular/src/directives/overlays/popover.ts @@ -1,131 +1,9 @@ -/* eslint-disable */ -/* tslint:disable */ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ContentChild, - ElementRef, - EventEmitter, - NgZone, - TemplateRef, -} from '@angular/core'; -import { ProxyCmp, proxyOutputs } from '../angular-component-lib/utils'; -import { Components } from '@ionic/core'; -export declare interface IonPopover extends Components.IonPopover { - /** - * Emitted after the popover has presented. - */ - ionPopoverDidPresent: EventEmitter; - /** - * Emitted before the popover has presented. - */ - ionPopoverWillPresent: EventEmitter; - /** - * Emitted after the popover has dismissed. - */ - ionPopoverWillDismiss: EventEmitter; - /** - * Emitted after the popover has dismissed. - */ - ionPopoverDidDismiss: EventEmitter; - /** - * Emitted after the popover has presented. Shorthand for ionPopoverWillDismiss. - */ - didPresent: EventEmitter; - /** - * Emitted before the popover has presented. Shorthand for ionPopoverWillPresent. - */ - willPresent: EventEmitter; - /** - * Emitted after the popover has presented. Shorthand for ionPopoverWillDismiss. - */ - willDismiss: EventEmitter; - /** - * Emitted after the popover has dismissed. Shorthand for ionPopoverDidDismiss. - */ - didDismiss: EventEmitter; -} -@ProxyCmp({ - inputs: [ - 'alignment', - 'animated', - 'arrow', - 'keepContentsMounted', - 'backdropDismiss', - 'cssClass', - 'dismissOnSelect', - 'enterAnimation', - 'event', - 'isOpen', - 'keyboardClose', - 'leaveAnimation', - 'mode', - 'showBackdrop', - 'translucent', - 'trigger', - 'triggerAction', - 'reference', - 'size', - 'side', - ], - methods: ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss'], -}) +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { IonPopover as IonPopoverBase } from '@ionic/angular/common'; + @Component({ selector: 'ion-popover', changeDetection: ChangeDetectionStrategy.OnPush, template: ``, - inputs: [ - 'alignment', - 'animated', - 'arrow', - 'keepContentsMounted', - 'backdropDismiss', - 'cssClass', - 'dismissOnSelect', - 'enterAnimation', - 'event', - 'isOpen', - 'keyboardClose', - 'leaveAnimation', - 'mode', - 'showBackdrop', - 'translucent', - 'trigger', - 'triggerAction', - 'reference', - 'size', - 'side', - ], }) -export class IonPopover { - // TODO(FW-2827): type - @ContentChild(TemplateRef, { static: false }) template: TemplateRef; - - isCmpOpen: boolean = false; - - protected el: HTMLElement; - - constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { - this.el = r.nativeElement; - - this.el.addEventListener('ionMount', () => { - this.isCmpOpen = true; - c.detectChanges(); - }); - this.el.addEventListener('didDismiss', () => { - this.isCmpOpen = false; - c.detectChanges(); - }); - proxyOutputs(this, this.el, [ - 'ionPopoverDidPresent', - 'ionPopoverWillPresent', - 'ionPopoverWillDismiss', - 'ionPopoverDidDismiss', - 'didPresent', - 'willPresent', - 'willDismiss', - 'didDismiss', - ]); - } -} +export class IonPopover extends IonPopoverBase {} diff --git a/packages/angular/standalone/src/index.ts b/packages/angular/standalone/src/index.ts index b8ddcc657e6..623279bdfa0 100644 --- a/packages/angular/standalone/src/index.ts +++ b/packages/angular/standalone/src/index.ts @@ -1,3 +1,4 @@ export { IonBackButton } from './navigation/back-button'; export { IonModal } from './overlays/modal'; +export { IonPopover } from './overlays/popover'; export { IonRouterOutlet } from './navigation/router-outlet'; diff --git a/packages/angular/standalone/src/overlays/popover.ts b/packages/angular/standalone/src/overlays/popover.ts new file mode 100644 index 00000000000..b94e2bd90f5 --- /dev/null +++ b/packages/angular/standalone/src/overlays/popover.ts @@ -0,0 +1,16 @@ +import { CommonModule } from '@angular/common'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { IonPopover as IonPopoverBase, ProxyCmp } from '@ionic/angular/common'; +import { defineCustomElement } from '@ionic/core/components/ion-popover.js'; + +@ProxyCmp({ + defineCustomElementFn: defineCustomElement, +}) +@Component({ + selector: 'ion-popover', + changeDetection: ChangeDetectionStrategy.OnPush, + template: ``, + standalone: true, + imports: [CommonModule], +}) +export class IonPopover extends IonPopoverBase {} diff --git a/packages/angular/test/base/e2e/src/standalone/popover.spec.ts b/packages/angular/test/base/e2e/src/standalone/popover.spec.ts new file mode 100644 index 00000000000..65aad046424 --- /dev/null +++ b/packages/angular/test/base/e2e/src/standalone/popover.spec.ts @@ -0,0 +1,12 @@ +describe('Popovers: Inline', () => { + beforeEach(() => { + cy.visit('/standalone/popover'); + }); + + it('should render popover', () => { + cy.get('button#open-popover').click(); + + cy.get('ion-popover').should('be.visible'); + cy.get('ion-popover #popover-content').should('exist'); + }); +}); diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts index c7233f2a3ed..71d183a6930 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts @@ -6,7 +6,7 @@ export const routes: Routes = [ path: '', component: AppComponent, children: [ - { path: 'test', loadComponent: () => import('../test/test.component').then(m => m.TestComponent) }, + { path: 'popover', loadComponent: () => import('../popover/popover.component').then(m => m.PopoverComponent) }, { path: 'modal', loadComponent: () => import('../modal/modal.component').then(m => m.ModalComponent) }, { path: 'router-outlet', loadComponent: () => import('../router-outlet/router-outlet.component').then(m => m.RouterOutletComponent) }, { path: 'back-button', loadComponent: () => import('../back-button/back-button.component').then(m => m.BackButtonComponent) }, diff --git a/packages/angular/test/base/src/app/standalone/popover/popover.component.html b/packages/angular/test/base/src/app/standalone/popover/popover.component.html new file mode 100644 index 00000000000..7860bb764a4 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/popover/popover.component.html @@ -0,0 +1,7 @@ + + + + +
This is my popover content
+
+
diff --git a/packages/angular/test/base/src/app/standalone/popover/popover.component.ts b/packages/angular/test/base/src/app/standalone/popover/popover.component.ts new file mode 100644 index 00000000000..cfb851f076e --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/popover/popover.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; +import { IonPopover } from '@ionic/angular/standalone'; + +@Component({ + selector: 'app-test', + templateUrl: './popover.component.html', + standalone: true, + imports: [IonPopover] +}) +export class PopoverComponent { +} diff --git a/packages/angular/test/base/src/app/standalone/test/test.component.html b/packages/angular/test/base/src/app/standalone/test/test.component.html deleted file mode 100644 index 0527e6bd2d7..00000000000 --- a/packages/angular/test/base/src/app/standalone/test/test.component.html +++ /dev/null @@ -1 +0,0 @@ -This is a test diff --git a/packages/angular/test/base/src/app/standalone/test/test.component.ts b/packages/angular/test/base/src/app/standalone/test/test.component.ts deleted file mode 100644 index 081b603159c..00000000000 --- a/packages/angular/test/base/src/app/standalone/test/test.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-test', - templateUrl: './test.component.html', - standalone: true -}) -export class TestComponent { - constructor() { - console.log('This should be undefined:', customElements.get('ion-app')); - } -} From c52a0972b952e987a7c9fe4d10c3122de53d9df0 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Fri, 4 Aug 2023 16:20:39 -0700 Subject: [PATCH 10/24] feat(angular): add standalone nav (#27876) --- .../src/directives/navigation/nav-delegate.ts | 44 +++++++++++-------- .../src/directives/navigation/nav-delegate.ts | 17 +++++-- packages/angular/standalone/src/index.ts | 1 + .../standalone/src/navigation/nav-delegate.ts | 24 ++++++++++ .../test/base/e2e/src/standalone/nav.spec.ts | 11 +++++ .../standalone/app-standalone/app.routes.ts | 1 + .../src/app/standalone/nav/nav.component.html | 1 + .../src/app/standalone/nav/nav.component.ts | 17 +++++++ .../app/standalone/nav/page-one.component.ts | 21 +++++++++ 9 files changed, 116 insertions(+), 21 deletions(-) create mode 100644 packages/angular/standalone/src/navigation/nav-delegate.ts create mode 100644 packages/angular/test/base/e2e/src/standalone/nav.spec.ts create mode 100644 packages/angular/test/base/src/app/standalone/nav/nav.component.html create mode 100644 packages/angular/test/base/src/app/standalone/nav/nav.component.ts create mode 100644 packages/angular/test/base/src/app/standalone/nav/page-one.component.ts diff --git a/packages/angular/common/src/directives/navigation/nav-delegate.ts b/packages/angular/common/src/directives/navigation/nav-delegate.ts index a9dfdd29042..009c2f17da0 100644 --- a/packages/angular/common/src/directives/navigation/nav-delegate.ts +++ b/packages/angular/common/src/directives/navigation/nav-delegate.ts @@ -1,28 +1,34 @@ -import { ElementRef, Injector, Directive, EnvironmentInjector } from '@angular/core'; +import { ElementRef, Injector, Directive, EnvironmentInjector, NgZone } from '@angular/core'; import { AngularDelegate } from '../../providers/angular-delegate'; import { ProxyCmp, proxyOutputs } from '../../utils/proxy'; +const NAV_INPUTS = ['animated', 'animation', 'root', 'rootParams', 'swipeGesture']; + +const NAV_METHODS = [ + 'push', + 'insert', + 'insertPages', + 'pop', + 'popTo', + 'popToRoot', + 'removeIndex', + 'setRoot', + 'setPages', + 'getActive', + 'getByIndex', + 'canGoBack', + 'getPrevious', +]; + @ProxyCmp({ - inputs: ['animated', 'animation', 'root', 'rootParams', 'swipeGesture'], - methods: [ - 'push', - 'insert', - 'insertPages', - 'pop', - 'popTo', - 'popToRoot', - 'removeIndex', - 'setRoot', - 'setPages', - 'getActive', - 'getByIndex', - 'canGoBack', - 'getPrevious', - ], + inputs: NAV_INPUTS, + methods: NAV_METHODS, }) @Directive({ selector: 'ion-nav', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: NAV_INPUTS, }) // eslint-disable-next-line @angular-eslint/directive-class-suffix export class NavDelegate { @@ -31,7 +37,9 @@ export class NavDelegate { ref: ElementRef, environmentInjector: EnvironmentInjector, injector: Injector, - angularDelegate: AngularDelegate + // TODO FW-4766: Remove AngularDelegate + angularDelegate: AngularDelegate, + protected z: NgZone ) { this.el = ref.nativeElement; ref.nativeElement.delegate = angularDelegate.create(environmentInjector, injector); diff --git a/packages/angular/src/directives/navigation/nav-delegate.ts b/packages/angular/src/directives/navigation/nav-delegate.ts index 000ccc060c6..d8c6d1231d0 100644 --- a/packages/angular/src/directives/navigation/nav-delegate.ts +++ b/packages/angular/src/directives/navigation/nav-delegate.ts @@ -1,8 +1,19 @@ -import { Directive } from '@angular/core'; -import { NavDelegate as NavDelegateBase } from '@ionic/angular/common'; +import { Directive, ElementRef, Injector, EnvironmentInjector, NgZone } from '@angular/core'; +import { NavDelegate as NavDelegateBase, AngularDelegate } from '@ionic/angular/common'; @Directive({ selector: 'ion-nav', }) // eslint-disable-next-line @angular-eslint/directive-class-suffix -export class NavDelegate extends NavDelegateBase {} +export class NavDelegate extends NavDelegateBase { + constructor( + ref: ElementRef, + environmentInjector: EnvironmentInjector, + injector: Injector, + // TODO FW-4766: Remove AngularDelegate + angularDelegate: AngularDelegate, + z: NgZone + ) { + super(ref, environmentInjector, injector, angularDelegate, z); + } +} diff --git a/packages/angular/standalone/src/index.ts b/packages/angular/standalone/src/index.ts index 623279bdfa0..29cb821250b 100644 --- a/packages/angular/standalone/src/index.ts +++ b/packages/angular/standalone/src/index.ts @@ -2,3 +2,4 @@ export { IonBackButton } from './navigation/back-button'; export { IonModal } from './overlays/modal'; export { IonPopover } from './overlays/popover'; export { IonRouterOutlet } from './navigation/router-outlet'; +export { IonNav } from './navigation/nav-delegate'; diff --git a/packages/angular/standalone/src/navigation/nav-delegate.ts b/packages/angular/standalone/src/navigation/nav-delegate.ts new file mode 100644 index 00000000000..62ad35144a2 --- /dev/null +++ b/packages/angular/standalone/src/navigation/nav-delegate.ts @@ -0,0 +1,24 @@ +import { Component, ElementRef, Injector, EnvironmentInjector, NgZone } from '@angular/core'; +import { NavDelegate as NavDelegateBase, ProxyCmp, AngularDelegate } from '@ionic/angular/common'; +import { defineCustomElement } from '@ionic/core/components/ion-nav.js'; + +@ProxyCmp({ + defineCustomElementFn: defineCustomElement, +}) +@Component({ + selector: 'ion-nav', + template: '', + standalone: true, +}) +export class IonNav extends NavDelegateBase { + constructor( + ref: ElementRef, + environmentInjector: EnvironmentInjector, + injector: Injector, + // TODO FW-4766: Remove AngularDelegate + angularDelegate: AngularDelegate, + z: NgZone + ) { + super(ref, environmentInjector, injector, angularDelegate, z); + } +} diff --git a/packages/angular/test/base/e2e/src/standalone/nav.spec.ts b/packages/angular/test/base/e2e/src/standalone/nav.spec.ts new file mode 100644 index 00000000000..dad653e838b --- /dev/null +++ b/packages/angular/test/base/e2e/src/standalone/nav.spec.ts @@ -0,0 +1,11 @@ +describe('Nav', () => { + beforeEach(() => { + cy.visit('/standalone/nav'); + }) + + it('should mount the root component', () => { + cy.ionPageVisible('app-nav'); + + cy.contains('Page One'); + }); +}) diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts index 71d183a6930..00c0bb74e92 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts @@ -10,6 +10,7 @@ export const routes: Routes = [ { path: 'modal', loadComponent: () => import('../modal/modal.component').then(m => m.ModalComponent) }, { path: 'router-outlet', loadComponent: () => import('../router-outlet/router-outlet.component').then(m => m.RouterOutletComponent) }, { path: 'back-button', loadComponent: () => import('../back-button/back-button.component').then(m => m.BackButtonComponent) }, + { path: 'nav', loadComponent: () => import('../nav/nav.component').then(m => m.NavComponent) }, ] }, ]; diff --git a/packages/angular/test/base/src/app/standalone/nav/nav.component.html b/packages/angular/test/base/src/app/standalone/nav/nav.component.html new file mode 100644 index 00000000000..687607161e9 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/nav/nav.component.html @@ -0,0 +1 @@ + diff --git a/packages/angular/test/base/src/app/standalone/nav/nav.component.ts b/packages/angular/test/base/src/app/standalone/nav/nav.component.ts new file mode 100644 index 00000000000..e056feca742 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/nav/nav.component.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; +import { IonNav } from '@ionic/angular/standalone'; +import { AngularDelegate } from '@ionic/angular/common'; + +import { PageOneComponent } from './page-one.component'; + +@Component({ + selector: 'app-nav', + templateUrl: './nav.component.html', + standalone: true, + imports: [IonNav], + // TODO FW-4766: Remove AngularDelegate from providers + providers: [AngularDelegate] +}) +export class NavComponent { + component = PageOneComponent; +} diff --git a/packages/angular/test/base/src/app/standalone/nav/page-one.component.ts b/packages/angular/test/base/src/app/standalone/nav/page-one.component.ts new file mode 100644 index 00000000000..aa90f9b5b98 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/nav/page-one.component.ts @@ -0,0 +1,21 @@ +/** + * TODO: Update the template to use `ion-nav-link` once it's implemented + * as a standalone component. + * + * It is recommended to also wait for the following components to be implemented + * as standalone components: + * `ion-header`, `ion-toolbar`, `ion-title`, `ion-content`, + * `ion-button`, `ion-buttons`, `ion-back-button` + */ + +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-page-one', + template: ` +
+

Page One

+
+ `, +}) +export class PageOneComponent {} From e44a02632dedea3758b5623e953f5d2d424af08b Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 8 Aug 2023 11:15:50 -0500 Subject: [PATCH 11/24] feat(angular): add standalone router-link (#27937) --- packages/angular/standalone/src/index.ts | 1 + .../src/navigation/router-link-delegate.ts | 21 ++++++++++++++++ .../e2e/src/standalone/router-link.spec.ts | 24 +++++++++++++++++++ .../standalone/app-standalone/app.routes.ts | 2 +- .../router-link/router-link.component.html | 6 +++++ .../router-link/router-link.component.ts | 11 +++++++++ 6 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 packages/angular/standalone/src/navigation/router-link-delegate.ts create mode 100644 packages/angular/test/base/e2e/src/standalone/router-link.spec.ts create mode 100644 packages/angular/test/base/src/app/standalone/router-link/router-link.component.html create mode 100644 packages/angular/test/base/src/app/standalone/router-link/router-link.component.ts diff --git a/packages/angular/standalone/src/index.ts b/packages/angular/standalone/src/index.ts index 29cb821250b..a044f6b986f 100644 --- a/packages/angular/standalone/src/index.ts +++ b/packages/angular/standalone/src/index.ts @@ -2,4 +2,5 @@ export { IonBackButton } from './navigation/back-button'; export { IonModal } from './overlays/modal'; export { IonPopover } from './overlays/popover'; export { IonRouterOutlet } from './navigation/router-outlet'; +export { IonRouterLink, IonRouterLinkWithHref } from './navigation/router-link-delegate'; export { IonNav } from './navigation/nav-delegate'; diff --git a/packages/angular/standalone/src/navigation/router-link-delegate.ts b/packages/angular/standalone/src/navigation/router-link-delegate.ts new file mode 100644 index 00000000000..5d4840f300a --- /dev/null +++ b/packages/angular/standalone/src/navigation/router-link-delegate.ts @@ -0,0 +1,21 @@ +import { Component } from '@angular/core'; +import { + RouterLinkDelegateDirective as RouterLinkDelegateBase, + RouterLinkWithHrefDelegateDirective as RouterLinkHrefDelegateBase, +} from '@ionic/angular/common'; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: ':not(a):not(area)[routerLink]', + template: '', + standalone: true, +}) +export class IonRouterLink extends RouterLinkDelegateBase {} + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'a[routerLink],area[routerLink]', + template: '', + standalone: true, +}) +export class IonRouterLinkWithHref extends RouterLinkHrefDelegateBase {} diff --git a/packages/angular/test/base/e2e/src/standalone/router-link.spec.ts b/packages/angular/test/base/e2e/src/standalone/router-link.spec.ts new file mode 100644 index 00000000000..ce8724876a9 --- /dev/null +++ b/packages/angular/test/base/e2e/src/standalone/router-link.spec.ts @@ -0,0 +1,24 @@ +describe('RouterLink', () => { + beforeEach(() => { + cy.visit('/standalone/router-link'); + }); + + it('should mount the root component', () => { + cy.ionPageVisible('app-router-link'); + + cy.contains('I\'m a link'); + }); + + it('should go to /standalone/popover using an anchor', () => { + // click on the anchor + cy.get('a').contains('I\'m a link').click(); + + cy.url().should('include', '/standalone/popover'); + }); + + it('should go to /standalone/popover using a button', () => { + cy.get('button').contains('I\'m a button').click(); + + cy.url().should('include', '/standalone/popover'); + }); +}); diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts index 00c0bb74e92..c43c86dd0fc 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts @@ -10,8 +10,8 @@ export const routes: Routes = [ { path: 'modal', loadComponent: () => import('../modal/modal.component').then(m => m.ModalComponent) }, { path: 'router-outlet', loadComponent: () => import('../router-outlet/router-outlet.component').then(m => m.RouterOutletComponent) }, { path: 'back-button', loadComponent: () => import('../back-button/back-button.component').then(m => m.BackButtonComponent) }, + { path: 'router-link', loadComponent: () => import('../router-link/router-link.component').then(m => m.RouterLinkComponent) }, { path: 'nav', loadComponent: () => import('../nav/nav.component').then(m => m.NavComponent) }, ] }, ]; - diff --git a/packages/angular/test/base/src/app/standalone/router-link/router-link.component.html b/packages/angular/test/base/src/app/standalone/router-link/router-link.component.html new file mode 100644 index 00000000000..d06acc30be7 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/router-link/router-link.component.html @@ -0,0 +1,6 @@ + + I'm a link + + diff --git a/packages/angular/test/base/src/app/standalone/router-link/router-link.component.ts b/packages/angular/test/base/src/app/standalone/router-link/router-link.component.ts new file mode 100644 index 00000000000..4e0e1bcff3c --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/router-link/router-link.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { IonRouterLink, IonRouterLinkWithHref } from '@ionic/angular/standalone'; + +@Component({ + selector: 'app-router-link', + templateUrl: './router-link.component.html', + standalone: true, + imports: [RouterModule, IonRouterLink, IonRouterLinkWithHref], +}) +export class RouterLinkComponent {} From 104b9547e55e76f768edca0a9df19017607967b7 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Tue, 15 Aug 2023 10:50:38 -0400 Subject: [PATCH 12/24] chore: typescript resolves @ionic/angular/common import paths (#27995) Issue number: N/A --------- ## What is the current behavior? IDEs that perform type checking/include a language service will error on the `@ionic/angular/common` import paths and not provide intelisense or auto import detection. ![CleanShot 2023-08-15 at 09 49 34](https://github.com/ionic-team/ionic-framework/assets/13732623/2e9913b2-5b44-4dd7-8cf7-8fa0d6aaac69) ## What is the new behavior? - `@ionic/angular/common` import paths are detected by IDEs such as VSCode ![CleanShot 2023-08-15 at 09 48 40](https://github.com/ionic-team/ionic-framework/assets/13732623/cd502093-b095-47a8-b5f7-b28a0fc9fe27) ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information --- packages/angular/tsconfig.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/angular/tsconfig.json b/packages/angular/tsconfig.json index 3e44846fb98..6d1a5a2ff02 100644 --- a/packages/angular/tsconfig.json +++ b/packages/angular/tsconfig.json @@ -35,8 +35,11 @@ { "name": "typescript-eslint-language-service" } - ] + ], + "paths": { + "@ionic/angular/common": ["common/src/index.ts"] + } }, "exclude": ["node_modules", "src/schematics"], - "files": ["src/index.ts"] + "files": ["src/index.ts", "common/src/index.ts"] } From 84212acce4229c7ba95faa63572a569571fad20e Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Tue, 15 Aug 2023 10:44:58 -0500 Subject: [PATCH 13/24] refactor(angular): use type for imports used as types (#27998) --- packages/angular/common/src/overlays/modal.ts | 2 +- packages/angular/common/src/overlays/popover.ts | 2 +- packages/angular/common/src/providers/picker-controller.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/angular/common/src/overlays/modal.ts b/packages/angular/common/src/overlays/modal.ts index 0ad1fd49c02..b1c3704f885 100644 --- a/packages/angular/common/src/overlays/modal.ts +++ b/packages/angular/common/src/overlays/modal.ts @@ -7,7 +7,7 @@ import { NgZone, TemplateRef, } from '@angular/core'; -import { Components, ModalBreakpointChangeEventDetail } from '@ionic/core'; +import type { Components, ModalBreakpointChangeEventDetail } from '@ionic/core/components'; import { ProxyCmp, proxyOutputs } from '../utils/proxy'; diff --git a/packages/angular/common/src/overlays/popover.ts b/packages/angular/common/src/overlays/popover.ts index 383eb75f15b..ab7cde3a66e 100644 --- a/packages/angular/common/src/overlays/popover.ts +++ b/packages/angular/common/src/overlays/popover.ts @@ -7,7 +7,7 @@ import { NgZone, TemplateRef, } from '@angular/core'; -import { Components } from '@ionic/core'; +import type { Components } from '@ionic/core/components'; import { ProxyCmp, proxyOutputs } from '../utils/proxy'; diff --git a/packages/angular/common/src/providers/picker-controller.ts b/packages/angular/common/src/providers/picker-controller.ts index 2baef2f4d48..f001a1e51c0 100644 --- a/packages/angular/common/src/providers/picker-controller.ts +++ b/packages/angular/common/src/providers/picker-controller.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import type { PickerOptions } from '@ionic/core'; +import type { PickerOptions } from '@ionic/core/components'; import { pickerController } from '@ionic/core/components'; import { OverlayBaseController } from '../utils/overlay'; From c65e08dcd1535638661d99f0d1cc658da0ed5953 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Tue, 15 Aug 2023 10:45:37 -0500 Subject: [PATCH 14/24] feat(angular): add standalone providers, route strategy, component binding provider (#27997) --- .../directives/navigation/router-outlet.ts | 24 +++++++++++++++++-- packages/angular/common/src/index.ts | 4 +++- .../src/utils/routing.ts} | 0 packages/angular/src/index.ts | 4 +--- packages/angular/src/ionic-module.ts | 21 ++-------------- packages/angular/standalone/src/index.ts | 19 +++++++++++++++ 6 files changed, 47 insertions(+), 25 deletions(-) rename packages/angular/{src/util/ionic-router-reuse-strategy.ts => common/src/utils/routing.ts} (100%) diff --git a/packages/angular/common/src/directives/navigation/router-outlet.ts b/packages/angular/common/src/directives/navigation/router-outlet.ts index e57a3faf854..6534bd55870 100644 --- a/packages/angular/common/src/directives/navigation/router-outlet.ts +++ b/packages/angular/common/src/directives/navigation/router-outlet.ts @@ -20,6 +20,7 @@ import { Injectable, reflectComponentType, } from '@angular/core'; +import type { Provider } from '@angular/core'; import { OutletContext, Router, ActivatedRoute, ChildrenOutletContexts, PRIMARY_OUTLET, Data } from '@angular/router'; import { componentOnReady } from '@ionic/core/components'; import type { AnimationBuilder } from '@ionic/core/components'; @@ -433,7 +434,7 @@ class OutletInjector implements Injector { } // TODO: FW-4785 - Remove this once Angular 15 support is dropped -export const INPUT_BINDER = new InjectionToken(''); +const INPUT_BINDER = new InjectionToken(''); /** * Injectable used as a tree-shakable provider for opting in to binding router data to component @@ -450,7 +451,7 @@ export const INPUT_BINDER = new InjectionToken(''); * the subscriptions are cleaned up. */ @Injectable() -export class RoutedComponentInputBinder { +class RoutedComponentInputBinder { private outletDataSubscriptions = new Map(); bindActivatedRouteToOutletComponent(outlet: IonRouterOutlet): void { @@ -507,3 +508,22 @@ export class RoutedComponentInputBinder { this.outletDataSubscriptions.set(outlet, dataSubscription); } } + +export const provideComponentInputBinding = (): Provider => { + return { + provide: INPUT_BINDER, + useFactory: componentInputBindingFactory, + deps: [Router], + }; +}; + +function componentInputBindingFactory(router?: Router) { + /** + * We cast the router to any here, since the componentInputBindingEnabled + * property is not available until Angular v16. + */ + if ((router as any)?.componentInputBindingEnabled) { + return new RoutedComponentInputBinder(); + } + return null; +} diff --git a/packages/angular/common/src/index.ts b/packages/angular/common/src/index.ts index 8cd0c975b42..a085304b89b 100644 --- a/packages/angular/common/src/index.ts +++ b/packages/angular/common/src/index.ts @@ -24,7 +24,7 @@ export { NavParams } from './directives/navigation/nav-params'; export { IonPopover } from './overlays/popover'; export { IonModal } from './overlays/modal'; -export { IonRouterOutlet, INPUT_BINDER, RoutedComponentInputBinder } from './directives/navigation/router-outlet'; +export { IonRouterOutlet, provideComponentInputBinding } from './directives/navigation/router-outlet'; export type { StackEvent } from './directives/navigation/stack-utils'; export { IonBackButtonDelegate } from './directives/navigation/back-button'; @@ -36,3 +36,5 @@ export { NavDelegate } from './directives/navigation/nav-delegate'; export { IonTabs } from './directives/navigation/tabs'; export { ProxyCmp } from './utils/proxy'; + +export { IonicRouteStrategy } from './utils/routing'; diff --git a/packages/angular/src/util/ionic-router-reuse-strategy.ts b/packages/angular/common/src/utils/routing.ts similarity index 100% rename from packages/angular/src/util/ionic-router-reuse-strategy.ts rename to packages/angular/common/src/utils/routing.ts diff --git a/packages/angular/src/index.ts b/packages/angular/src/index.ts index ee3d20c6515..b90483fa623 100644 --- a/packages/angular/src/index.ts +++ b/packages/angular/src/index.ts @@ -35,11 +35,9 @@ export { Platform, AngularDelegate, NavParams, + IonicRouteStrategy, } from '@ionic/angular/common'; -// ROUTER STRATEGY -export { IonicRouteStrategy } from './util/ionic-router-reuse-strategy'; - // TYPES export * from './types/ionic-lifecycle-hooks'; diff --git a/packages/angular/src/ionic-module.ts b/packages/angular/src/ionic-module.ts index d24c4b727ac..251d78f591e 100644 --- a/packages/angular/src/ionic-module.ts +++ b/packages/angular/src/ionic-module.ts @@ -1,13 +1,11 @@ import { CommonModule, DOCUMENT } from '@angular/common'; import { ModuleWithProviders, APP_INITIALIZER, NgModule, NgZone } from '@angular/core'; -import { Router } from '@angular/router'; import { ModalController, PopoverController, ConfigToken, AngularDelegate, - INPUT_BINDER, - RoutedComponentInputBinder, + provideComponentInputBinding, } from '@ionic/angular/common'; import { IonicConfig } from '@ionic/core'; @@ -76,23 +74,8 @@ export class IonicModule { multi: true, deps: [ConfigToken, DOCUMENT, NgZone], }, - { - provide: INPUT_BINDER, - useFactory: componentInputBindingFactory, - deps: [Router], - }, + provideComponentInputBinding(), ], }; } } - -function componentInputBindingFactory(router?: Router) { - /** - * We cast the router to any here, since the componentInputBindingEnabled - * property is not available until Angular v16. - */ - if ((router as any)?.componentInputBindingEnabled) { - return new RoutedComponentInputBinder(); - } - return null; -} diff --git a/packages/angular/standalone/src/index.ts b/packages/angular/standalone/src/index.ts index a044f6b986f..a1cdeac984c 100644 --- a/packages/angular/standalone/src/index.ts +++ b/packages/angular/standalone/src/index.ts @@ -4,3 +4,22 @@ export { IonPopover } from './overlays/popover'; export { IonRouterOutlet } from './navigation/router-outlet'; export { IonRouterLink, IonRouterLinkWithHref } from './navigation/router-link-delegate'; export { IonNav } from './navigation/nav-delegate'; + +export { + ActionSheetController, + AlertController, + LoadingController, + MenuController, + ModalController, + PickerController, + PopoverController, + ToastController, + AnimationController, + GestureController, + DomController, + NavController, + Config, + Platform, + NavParams, + IonicRouteStrategy, +} from '@ionic/angular/common'; From ca536826846a19536d2b8267f56aa83d37a8cd19 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Tue, 15 Aug 2023 14:32:27 -0500 Subject: [PATCH 15/24] feat(angular): add standalone provideIonicAngular (#27996) --- .../src/directives/navigation/nav-delegate.ts | 1 - .../src/directives/navigation/nav-delegate.ts | 1 - packages/angular/standalone/src/index.ts | 2 +- .../standalone/src/navigation/nav-delegate.ts | 1 - .../standalone/src/providers/ionic-angular.ts | 51 +++++++++++++++++++ .../test/apps/ng14/src/main-standalone.ts | 23 +++++++++ .../standalone/overlay-controllers.spec.ts | 17 +++++++ .../base/e2e/src/standalone/providers.spec.ts | 11 ++++ .../base/src/app/app-standalone.component.ts | 11 ++++ .../app-standalone/app.component.ts | 9 ---- .../standalone/app-standalone/app.routes.ts | 2 + .../src/app/standalone/nav/nav.component.ts | 4 +- .../overlay-controllers.component.html | 4 ++ .../overlay-controllers.component.ts | 36 +++++++++++++ .../providers/providers.component.html | 4 ++ .../providers/providers.component.ts | 15 ++++++ .../angular/test/base/src/main-standalone.ts | 17 +++++++ packages/angular/test/base/src/main.ts | 22 ++++++-- 18 files changed, 210 insertions(+), 21 deletions(-) create mode 100644 packages/angular/standalone/src/providers/ionic-angular.ts create mode 100644 packages/angular/test/apps/ng14/src/main-standalone.ts create mode 100644 packages/angular/test/base/e2e/src/standalone/overlay-controllers.spec.ts create mode 100644 packages/angular/test/base/e2e/src/standalone/providers.spec.ts create mode 100644 packages/angular/test/base/src/app/app-standalone.component.ts create mode 100644 packages/angular/test/base/src/app/standalone/overlay-controllers/overlay-controllers.component.html create mode 100644 packages/angular/test/base/src/app/standalone/overlay-controllers/overlay-controllers.component.ts create mode 100644 packages/angular/test/base/src/app/standalone/providers/providers.component.html create mode 100644 packages/angular/test/base/src/app/standalone/providers/providers.component.ts create mode 100644 packages/angular/test/base/src/main-standalone.ts diff --git a/packages/angular/common/src/directives/navigation/nav-delegate.ts b/packages/angular/common/src/directives/navigation/nav-delegate.ts index 009c2f17da0..58429961dbf 100644 --- a/packages/angular/common/src/directives/navigation/nav-delegate.ts +++ b/packages/angular/common/src/directives/navigation/nav-delegate.ts @@ -37,7 +37,6 @@ export class NavDelegate { ref: ElementRef, environmentInjector: EnvironmentInjector, injector: Injector, - // TODO FW-4766: Remove AngularDelegate angularDelegate: AngularDelegate, protected z: NgZone ) { diff --git a/packages/angular/src/directives/navigation/nav-delegate.ts b/packages/angular/src/directives/navigation/nav-delegate.ts index d8c6d1231d0..9d61cd5d655 100644 --- a/packages/angular/src/directives/navigation/nav-delegate.ts +++ b/packages/angular/src/directives/navigation/nav-delegate.ts @@ -10,7 +10,6 @@ export class NavDelegate extends NavDelegateBase { ref: ElementRef, environmentInjector: EnvironmentInjector, injector: Injector, - // TODO FW-4766: Remove AngularDelegate angularDelegate: AngularDelegate, z: NgZone ) { diff --git a/packages/angular/standalone/src/index.ts b/packages/angular/standalone/src/index.ts index a1cdeac984c..4f012c4cd3b 100644 --- a/packages/angular/standalone/src/index.ts +++ b/packages/angular/standalone/src/index.ts @@ -4,7 +4,7 @@ export { IonPopover } from './overlays/popover'; export { IonRouterOutlet } from './navigation/router-outlet'; export { IonRouterLink, IonRouterLinkWithHref } from './navigation/router-link-delegate'; export { IonNav } from './navigation/nav-delegate'; - +export { provideIonicAngular } from './providers/ionic-angular'; export { ActionSheetController, AlertController, diff --git a/packages/angular/standalone/src/navigation/nav-delegate.ts b/packages/angular/standalone/src/navigation/nav-delegate.ts index 62ad35144a2..410f45e635c 100644 --- a/packages/angular/standalone/src/navigation/nav-delegate.ts +++ b/packages/angular/standalone/src/navigation/nav-delegate.ts @@ -15,7 +15,6 @@ export class IonNav extends NavDelegateBase { ref: ElementRef, environmentInjector: EnvironmentInjector, injector: Injector, - // TODO FW-4766: Remove AngularDelegate angularDelegate: AngularDelegate, z: NgZone ) { diff --git a/packages/angular/standalone/src/providers/ionic-angular.ts b/packages/angular/standalone/src/providers/ionic-angular.ts new file mode 100644 index 00000000000..8dd0053cc0b --- /dev/null +++ b/packages/angular/standalone/src/providers/ionic-angular.ts @@ -0,0 +1,51 @@ +import { DOCUMENT } from '@angular/common'; +import { APP_INITIALIZER } from '@angular/core'; +import type { Provider } from '@angular/core'; +import { + AngularDelegate, + ConfigToken, + ModalController, + PopoverController, + provideComponentInputBinding, +} from '@ionic/angular/common'; +import { initialize } from '@ionic/core/components'; +import type { IonicConfig } from '@ionic/core/components'; + +export const provideIonicAngular = (config?: IonicConfig): Provider[] => { + /** + * TODO FW-4967 + * Use makeEnvironmentProviders once Angular 14 support is dropped. + * This prevents provideIonicAngular from being accidentally referenced in an @Component. + */ + return [ + { + provide: ConfigToken, + useValue: config, + }, + { + provide: APP_INITIALIZER, + useFactory: initializeIonicAngular, + multi: true, + deps: [ConfigToken, DOCUMENT], + }, + provideComponentInputBinding(), + AngularDelegate, + ModalController, + PopoverController, + ]; +}; + +const initializeIonicAngular = (config: IonicConfig, doc: Document) => { + return () => { + /** + * By default Ionic Framework hides elements that + * are not hydrated, but in the CE build there is no + * hydration. + * TODO FW-2797: Remove when all integrations have been + * migrated to CE build. + */ + doc.documentElement.classList.add('ion-ce'); + + initialize(config); + }; +}; diff --git a/packages/angular/test/apps/ng14/src/main-standalone.ts b/packages/angular/test/apps/ng14/src/main-standalone.ts new file mode 100644 index 00000000000..18e84fb933f --- /dev/null +++ b/packages/angular/test/apps/ng14/src/main-standalone.ts @@ -0,0 +1,23 @@ +import { importProvidersFrom } from '@angular/core'; +import { bootstrapApplication } from '@angular/platform-browser'; +import { RouteReuseStrategy } from '@angular/router'; +import { provideIonicAngular, IonicRouteStrategy } from '@ionic/angular/standalone'; + +import { AppComponentStandalone } from './app/app-standalone.component'; +import { AppRoutingModule } from './app/app-routing.module'; + +import { routes } from './app/app.routes'; + +export const bootstrapStandalone = () => { + bootstrapApplication(AppComponentStandalone, { + providers: [ + { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, + /** + * provideRouter is not available in Angular 14, so + * we fallback to using AppRoutingModule + */ + importProvidersFrom(AppRoutingModule), + provideIonicAngular({ keyboardHeight: 12345 }) + ], + }); +} diff --git a/packages/angular/test/base/e2e/src/standalone/overlay-controllers.spec.ts b/packages/angular/test/base/e2e/src/standalone/overlay-controllers.spec.ts new file mode 100644 index 00000000000..e48982c7fe0 --- /dev/null +++ b/packages/angular/test/base/e2e/src/standalone/overlay-controllers.spec.ts @@ -0,0 +1,17 @@ +describe('Overlay Controllers', () => { + beforeEach(() => { + cy.visit('/standalone/overlay-controllers'); + }) + + it('should present a modal', () => { + cy.get('button#open-modal').click(); + + cy.get('ion-modal app-dialog-content').should('be.visible'); + }); + + it('should present a popover', () => { + cy.get('button#open-popover').click(); + + cy.get('ion-popover app-dialog-content').should('be.visible'); + }); +}) diff --git a/packages/angular/test/base/e2e/src/standalone/providers.spec.ts b/packages/angular/test/base/e2e/src/standalone/providers.spec.ts new file mode 100644 index 00000000000..71aca1c635c --- /dev/null +++ b/packages/angular/test/base/e2e/src/standalone/providers.spec.ts @@ -0,0 +1,11 @@ +describe('Providers', () => { + beforeEach(() => { + cy.visit('/standalone/providers'); + }) + + it('provideIonicAngular should initialize Ionic and set config correctly', () => { + cy.ionPageVisible('app-providers'); + + cy.get('#keyboard-height').should('have.text', '12345'); + }); +}) diff --git a/packages/angular/test/base/src/app/app-standalone.component.ts b/packages/angular/test/base/src/app/app-standalone.component.ts new file mode 100644 index 00000000000..e39f6d36618 --- /dev/null +++ b/packages/angular/test/base/src/app/app-standalone.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + standalone: true, + imports: [RouterModule] +}) +export class AppComponentStandalone { +} diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.component.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.component.ts index 280de0d8c76..e1058486487 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.component.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.component.ts @@ -1,15 +1,6 @@ import { Component } from '@angular/core'; import { RouterModule } from '@angular/router'; import { IonRouterOutlet } from '@ionic/angular/standalone'; -/** - * This temporary code initialized Ionic and ensures components are visible. - * TODO FW-4766 Can be removed when ticket is implemented - */ -import { initialize } from '@ionic/core/components'; -initialize(); - -document.querySelector('html')!.classList.add('ion-ce') - @Component({ selector: 'app-root-standalone', diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts index c43c86dd0fc..fb027e34f98 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts @@ -12,6 +12,8 @@ export const routes: Routes = [ { path: 'back-button', loadComponent: () => import('../back-button/back-button.component').then(m => m.BackButtonComponent) }, { path: 'router-link', loadComponent: () => import('../router-link/router-link.component').then(m => m.RouterLinkComponent) }, { path: 'nav', loadComponent: () => import('../nav/nav.component').then(m => m.NavComponent) }, + { path: 'providers', loadComponent: () => import('../providers/providers.component').then(m => m.ProvidersComponent) }, + { path: 'overlay-controllers', loadComponent: () => import('../overlay-controllers/overlay-controllers.component').then(m => m.OverlayControllersComponent) }, ] }, ]; diff --git a/packages/angular/test/base/src/app/standalone/nav/nav.component.ts b/packages/angular/test/base/src/app/standalone/nav/nav.component.ts index e056feca742..b969f7683c2 100644 --- a/packages/angular/test/base/src/app/standalone/nav/nav.component.ts +++ b/packages/angular/test/base/src/app/standalone/nav/nav.component.ts @@ -8,9 +8,7 @@ import { PageOneComponent } from './page-one.component'; selector: 'app-nav', templateUrl: './nav.component.html', standalone: true, - imports: [IonNav], - // TODO FW-4766: Remove AngularDelegate from providers - providers: [AngularDelegate] + imports: [IonNav] }) export class NavComponent { component = PageOneComponent; diff --git a/packages/angular/test/base/src/app/standalone/overlay-controllers/overlay-controllers.component.html b/packages/angular/test/base/src/app/standalone/overlay-controllers/overlay-controllers.component.html new file mode 100644 index 00000000000..b05f9b2530a --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/overlay-controllers/overlay-controllers.component.html @@ -0,0 +1,4 @@ +
+ + +
diff --git a/packages/angular/test/base/src/app/standalone/overlay-controllers/overlay-controllers.component.ts b/packages/angular/test/base/src/app/standalone/overlay-controllers/overlay-controllers.component.ts new file mode 100644 index 00000000000..5c20a02bd92 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/overlay-controllers/overlay-controllers.component.ts @@ -0,0 +1,36 @@ +import { Component } from '@angular/core'; +import { ModalController, PopoverController } from '@ionic/angular/standalone'; + +@Component({ + selector: 'app-overlay-controllers', + templateUrl: './overlay-controllers.component.html', + standalone: true, +}) +export class OverlayControllersComponent { + constructor(private modalCtrl: ModalController, private popoverCtrl: PopoverController) {} + + async openModal() { + const modal = await this.modalCtrl.create({ + component: DialogComponent + }); + + await modal.present(); + } + + async openPopover(ev: MouseEvent) { + const popover = await this.popoverCtrl.create({ + component: DialogComponent, + event: ev + }); + + await popover.present(); + } +} + +@Component({ + selector: 'app-dialog-content', + template: '
Dialog Content
', + standalone: true, +}) +class DialogComponent { +} diff --git a/packages/angular/test/base/src/app/standalone/providers/providers.component.html b/packages/angular/test/base/src/app/standalone/providers/providers.component.html new file mode 100644 index 00000000000..c0bb11bca7d --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/providers/providers.component.html @@ -0,0 +1,4 @@ +
    +
  • Keyboard Height: {{ keyboardHeight }}
  • +
+ diff --git a/packages/angular/test/base/src/app/standalone/providers/providers.component.ts b/packages/angular/test/base/src/app/standalone/providers/providers.component.ts new file mode 100644 index 00000000000..8dcb9017c1a --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/providers/providers.component.ts @@ -0,0 +1,15 @@ +import { Component } from '@angular/core'; +import { Config } from '@ionic/angular/standalone'; + +@Component({ + selector: 'app-providers', + templateUrl: './providers.component.html', + standalone: true, +}) +export class ProvidersComponent { + keyboardHeight?: number; + + constructor(private config: Config) { + this.keyboardHeight = config.get('keyboardHeight'); + } +} diff --git a/packages/angular/test/base/src/main-standalone.ts b/packages/angular/test/base/src/main-standalone.ts new file mode 100644 index 00000000000..d2dfd98d8f2 --- /dev/null +++ b/packages/angular/test/base/src/main-standalone.ts @@ -0,0 +1,17 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { RouteReuseStrategy, provideRouter } from '@angular/router'; +import { provideIonicAngular, IonicRouteStrategy } from '@ionic/angular/standalone'; + +import { AppComponentStandalone } from './app/app-standalone.component'; + +import { routes } from './app/app.routes'; + +export const bootstrapStandalone = () => { + bootstrapApplication(AppComponentStandalone, { + providers: [ + { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, + provideRouter(routes), + provideIonicAngular({ keyboardHeight: 12345 }) + ], + }); +} diff --git a/packages/angular/test/base/src/main.ts b/packages/angular/test/base/src/main.ts index e6659e9a13c..5c0debfa146 100644 --- a/packages/angular/test/base/src/main.ts +++ b/packages/angular/test/base/src/main.ts @@ -8,8 +8,20 @@ if (environment.production) { enableProdMode(); } -document.addEventListener('DOMContentLoaded', () => { - platformBrowserDynamic() - .bootstrapModule(AppModule) - .catch(err => console.error(err)); -}); +const isLazy = window.location.href.includes('lazy'); + +if (isLazy) { + document.addEventListener('DOMContentLoaded', () => { + platformBrowserDynamic() + .bootstrapModule(AppModule) + .catch(err => console.error(err)); + }); +} else { + /** + * Importing standalone and lazy modules in the same + * file creates side effects where manually generated components + * such as ion-modal do not get bootstrapped correctly. Using + * a dynamic import avoids this. + */ + import('./main-standalone').then((module) => { module.bootstrapStandalone() }); +} From e57759f4b6b30bb8ebef5a1021e5ad5edad4dbf1 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Fri, 18 Aug 2023 11:02:16 -0400 Subject: [PATCH 16/24] chore(angular): generate standalone component wrappers (#27970) Issue number: N/A --------- ## What is the current behavior? Ionic Framework currently only generates the lazy/hydrated Angular component wrappers for the web components. ## What is the new behavior? - Generates Angular standalone component wrappers for the web components, using the CE build. - Adds manual component wrapper for `ion-icon` with the CE build. - Migrates the `ion-back-button` and `ion-nav` to be manual component wrappers - Continues to generate the lazy/hydrated Angular component wrappers. - Refactors "NavDelegate" etc. language to `IonNav` to avoid exporting as and simplify navigating the code. ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information Related output targets PR: https://github.com/ionic-team/stencil-ds-output-targets/pull/367 --- core/package-lock.json | 14 +- core/package.json | 2 +- core/stencil.config.ts | 79 +- .../src/directives/navigation/back-button.ts | 17 +- .../navigation/{nav-delegate.ts => nav.ts} | 13 +- packages/angular/common/src/index.ts | 4 +- .../directives/navigation/ion-back-button.ts | 15 +- .../src/directives/navigation/ion-nav.ts | 29 + .../src/directives/navigation/nav-delegate.ts | 18 - .../angular/src/directives/proxies-list.ts | 2 - packages/angular/src/directives/proxies.ts | 55 - packages/angular/src/index.ts | 4 +- packages/angular/src/ionic-module.ts | 8 +- .../directives/angular-component-lib/utils.ts | 57 + .../angular/standalone/src/directives/icon.ts | 24 + .../standalone/src/directives/proxies.ts | 3159 +++++++++++++++++ packages/angular/standalone/src/index.ts | 4 +- .../standalone/src/navigation/back-button.ts | 16 +- .../navigation/{nav-delegate.ts => nav.ts} | 11 +- .../app-standalone/app.component.html | 4 +- .../app-standalone/app.component.ts | 4 +- .../standalone/app-standalone/app.routes.ts | 17 +- .../app/standalone/button/button.component.ts | 12 + 23 files changed, 3408 insertions(+), 160 deletions(-) rename packages/angular/common/src/directives/navigation/{nav-delegate.ts => nav.ts} (73%) create mode 100644 packages/angular/src/directives/navigation/ion-nav.ts delete mode 100644 packages/angular/src/directives/navigation/nav-delegate.ts create mode 100644 packages/angular/standalone/src/directives/angular-component-lib/utils.ts create mode 100644 packages/angular/standalone/src/directives/icon.ts create mode 100644 packages/angular/standalone/src/directives/proxies.ts rename packages/angular/standalone/src/navigation/{nav-delegate.ts => nav.ts} (69%) create mode 100644 packages/angular/test/base/src/app/standalone/button/button.component.ts diff --git a/core/package-lock.json b/core/package-lock.json index 068866c9e91..6c352a9581b 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -25,7 +25,7 @@ "@playwright/test": "^1.36.1", "@rollup/plugin-node-resolve": "^8.4.0", "@rollup/plugin-virtual": "^2.0.3", - "@stencil/angular-output-target": "^0.7.1", + "@stencil/angular-output-target": "^0.8.1", "@stencil/react-output-target": "^0.5.3", "@stencil/sass": "^3.0.4", "@stencil/vue-output-target": "^0.8.6", @@ -1625,9 +1625,9 @@ } }, "node_modules/@stencil/angular-output-target": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@stencil/angular-output-target/-/angular-output-target-0.7.1.tgz", - "integrity": "sha512-lxJbCAbyAQVAKGgEpNTjSF7GZZszbrJnNdNVgzuD1hLRFJyElA6kUSL0GQrZMbiPG5lC/cYdbQwpyWHX4xN8mw==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@stencil/angular-output-target/-/angular-output-target-0.8.1.tgz", + "integrity": "sha512-nO04lC7JxUT0bi2sTvBslz6aut1dVOvYRckf29a0l08XnRsDzIfT5e6f+c5HL9vCl4UFRq8fTQ+Og/G7Hlwtng==", "dev": true, "peerDependencies": { "@stencil/core": ">=2.0.0 || >=3 || >= 4.0.0-beta.0 || >= 4.0.0" @@ -11513,9 +11513,9 @@ } }, "@stencil/angular-output-target": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@stencil/angular-output-target/-/angular-output-target-0.7.1.tgz", - "integrity": "sha512-lxJbCAbyAQVAKGgEpNTjSF7GZZszbrJnNdNVgzuD1hLRFJyElA6kUSL0GQrZMbiPG5lC/cYdbQwpyWHX4xN8mw==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@stencil/angular-output-target/-/angular-output-target-0.8.1.tgz", + "integrity": "sha512-nO04lC7JxUT0bi2sTvBslz6aut1dVOvYRckf29a0l08XnRsDzIfT5e6f+c5HL9vCl4UFRq8fTQ+Og/G7Hlwtng==", "dev": true, "requires": {} }, diff --git a/core/package.json b/core/package.json index 735a09849bd..57f3f1f09a4 100644 --- a/core/package.json +++ b/core/package.json @@ -47,7 +47,7 @@ "@playwright/test": "^1.36.1", "@rollup/plugin-node-resolve": "^8.4.0", "@rollup/plugin-virtual": "^2.0.3", - "@stencil/angular-output-target": "^0.7.1", + "@stencil/angular-output-target": "^0.8.1", "@stencil/react-output-target": "^0.5.3", "@stencil/sass": "^3.0.4", "@stencil/vue-output-target": "^0.8.6", diff --git a/core/stencil.config.ts b/core/stencil.config.ts index 44157e62d9c..300a8fd6347 100644 --- a/core/stencil.config.ts +++ b/core/stencil.config.ts @@ -7,6 +7,56 @@ import { vueOutputTarget } from '@stencil/vue-output-target'; // @ts-ignore import { apiSpecGenerator } from './scripts/api-spec-generator'; +const componentCorePackage = '@ionic/core'; + +const getAngularOutputTargets = () => { + const excludeComponents = [ + // overlays that accept user components + 'ion-modal', + 'ion-popover', + + // navigation + 'ion-router', + 'ion-route', + 'ion-route-redirect', + 'ion-router-link', + 'ion-router-outlet', + 'ion-nav', + 'ion-back-button', + + // tabs + 'ion-tabs', + 'ion-tab', + + // auxiliar + 'ion-picker-column', + ] + return [ + angularOutputTarget({ + componentCorePackage, + directivesProxyFile: '../packages/angular/src/directives/proxies.ts', + directivesArrayFile: '../packages/angular/src/directives/proxies-list.ts', + excludeComponents, + outputType: 'component', + }), + angularOutputTarget({ + componentCorePackage, + directivesProxyFile: '../packages/angular/standalone/src/directives/proxies.ts', + excludeComponents: [ + ...excludeComponents, + /** + * IonIcon is a special case because it does not come + * from the `@ionic/core` package, so generating proxies that + * are reliant on the CE build will reference the wrong + * import location. + */ + 'ion-icon' + ], + outputType: 'standalone', + }) + ]; +} + export const config: Config = { autoprefixCss: true, sourceMap: false, @@ -61,7 +111,7 @@ export const config: Config = { ], outputTargets: [ reactOutputTarget({ - componentCorePackage: '@ionic/core', + componentCorePackage, includeImportCustomElements: true, includePolyfills: false, includeDefineCustomElements: false, @@ -98,7 +148,7 @@ export const config: Config = { ] }), vueOutputTarget({ - componentCorePackage: '@ionic/core', + componentCorePackage, includeImportCustomElements: true, includePolyfills: false, includeDefineCustomElements: false, @@ -182,30 +232,7 @@ export const config: Config = { // type: 'stats', // file: 'stats.json' // }, - angularOutputTarget({ - componentCorePackage: '@ionic/core', - directivesProxyFile: '../packages/angular/src/directives/proxies.ts', - directivesArrayFile: '../packages/angular/src/directives/proxies-list.ts', - excludeComponents: [ - // overlays that accept user components - 'ion-modal', - 'ion-popover', - - // navigation - 'ion-router', - 'ion-route', - 'ion-route-redirect', - 'ion-router-link', - 'ion-router-outlet', - - // tabs - 'ion-tabs', - 'ion-tab', - - // auxiliar - 'ion-picker-column', - ], - }), + ...getAngularOutputTargets(), ], buildEs5: 'prod', testing: { diff --git a/packages/angular/common/src/directives/navigation/back-button.ts b/packages/angular/common/src/directives/navigation/back-button.ts index 7ec7136a8c4..cdad44d165e 100644 --- a/packages/angular/common/src/directives/navigation/back-button.ts +++ b/packages/angular/common/src/directives/navigation/back-button.ts @@ -1,4 +1,5 @@ -import { Directive, HostListener, Input, Optional, ElementRef, NgZone } from '@angular/core'; +import { HostListener, Input, Optional, ElementRef, NgZone, ChangeDetectorRef, Directive } from '@angular/core'; +import type { Components } from '@ionic/core'; import type { AnimationBuilder } from '@ionic/core/components'; import { Config } from '../../providers/config'; @@ -9,21 +10,23 @@ import { IonRouterOutlet } from './router-outlet'; const BACK_BUTTON_INPUTS = ['color', 'defaultHref', 'disabled', 'icon', 'mode', 'routerAnimation', 'text', 'type']; +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export declare interface IonBackButton extends Components.IonBackButton {} + @ProxyCmp({ inputs: BACK_BUTTON_INPUTS, }) @Directive({ - selector: 'ion-back-button', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property inputs: BACK_BUTTON_INPUTS, }) // eslint-disable-next-line @angular-eslint/directive-class-suffix -export class IonBackButtonDelegate { +export class IonBackButton { @Input() - defaultHref: string | undefined | null; + defaultHref: string | undefined; @Input() - routerAnimation?: AnimationBuilder; + routerAnimation: AnimationBuilder | undefined; protected el: HTMLElement; @@ -32,8 +35,10 @@ export class IonBackButtonDelegate { private navCtrl: NavController, private config: Config, private r: ElementRef, - protected z: NgZone + protected z: NgZone, + c: ChangeDetectorRef ) { + c.detach(); this.el = this.r.nativeElement; } diff --git a/packages/angular/common/src/directives/navigation/nav-delegate.ts b/packages/angular/common/src/directives/navigation/nav.ts similarity index 73% rename from packages/angular/common/src/directives/navigation/nav-delegate.ts rename to packages/angular/common/src/directives/navigation/nav.ts index 58429961dbf..585bffc0400 100644 --- a/packages/angular/common/src/directives/navigation/nav-delegate.ts +++ b/packages/angular/common/src/directives/navigation/nav.ts @@ -1,4 +1,5 @@ -import { ElementRef, Injector, Directive, EnvironmentInjector, NgZone } from '@angular/core'; +import { ElementRef, Injector, EnvironmentInjector, NgZone, ChangeDetectorRef, Directive } from '@angular/core'; +import type { Components } from '@ionic/core'; import { AngularDelegate } from '../../providers/angular-delegate'; import { ProxyCmp, proxyOutputs } from '../../utils/proxy'; @@ -21,25 +22,29 @@ const NAV_METHODS = [ 'getPrevious', ]; +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export declare interface IonNav extends Components.IonNav {} + @ProxyCmp({ inputs: NAV_INPUTS, methods: NAV_METHODS, }) @Directive({ - selector: 'ion-nav', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property inputs: NAV_INPUTS, }) // eslint-disable-next-line @angular-eslint/directive-class-suffix -export class NavDelegate { +export class IonNav { protected el: HTMLElement; constructor( ref: ElementRef, environmentInjector: EnvironmentInjector, injector: Injector, angularDelegate: AngularDelegate, - protected z: NgZone + protected z: NgZone, + c: ChangeDetectorRef ) { + c.detach(); this.el = ref.nativeElement; ref.nativeElement.delegate = angularDelegate.create(environmentInjector, injector); proxyOutputs(this, this.el, ['ionNavDidChange', 'ionNavWillChange']); diff --git a/packages/angular/common/src/index.ts b/packages/angular/common/src/index.ts index a085304b89b..cab9a8ea87d 100644 --- a/packages/angular/common/src/index.ts +++ b/packages/angular/common/src/index.ts @@ -27,12 +27,12 @@ export { IonModal } from './overlays/modal'; export { IonRouterOutlet, provideComponentInputBinding } from './directives/navigation/router-outlet'; export type { StackEvent } from './directives/navigation/stack-utils'; -export { IonBackButtonDelegate } from './directives/navigation/back-button'; +export { IonBackButton } from './directives/navigation/back-button'; export { RouterLinkDelegateDirective, RouterLinkWithHrefDelegateDirective, } from './directives/navigation/router-link-delegate'; -export { NavDelegate } from './directives/navigation/nav-delegate'; +export { IonNav } from './directives/navigation/nav'; export { IonTabs } from './directives/navigation/tabs'; export { ProxyCmp } from './utils/proxy'; diff --git a/packages/angular/src/directives/navigation/ion-back-button.ts b/packages/angular/src/directives/navigation/ion-back-button.ts index 309a5cf53c5..abfbebfeb36 100644 --- a/packages/angular/src/directives/navigation/ion-back-button.ts +++ b/packages/angular/src/directives/navigation/ion-back-button.ts @@ -1,20 +1,23 @@ -import { Directive, Optional, ElementRef, NgZone } from '@angular/core'; -import { IonBackButtonDelegate as IonBackButtonDelegateBase, NavController, Config } from '@ionic/angular/common'; +import { Optional, ElementRef, NgZone, ChangeDetectorRef, Component, ChangeDetectionStrategy } from '@angular/core'; +import { IonBackButton as IonBackButtonBase, NavController, Config } from '@ionic/angular/common'; import { IonRouterOutlet } from './ion-router-outlet'; -@Directive({ +@Component({ selector: 'ion-back-button', + template: '', + changeDetection: ChangeDetectionStrategy.OnPush, }) // eslint-disable-next-line @angular-eslint/directive-class-suffix -export class IonBackButtonDelegateDirective extends IonBackButtonDelegateBase { +export class IonBackButton extends IonBackButtonBase { constructor( @Optional() routerOutlet: IonRouterOutlet, navCtrl: NavController, config: Config, r: ElementRef, - z: NgZone + z: NgZone, + c: ChangeDetectorRef ) { - super(routerOutlet, navCtrl, config, r, z); + super(routerOutlet, navCtrl, config, r, z, c); } } diff --git a/packages/angular/src/directives/navigation/ion-nav.ts b/packages/angular/src/directives/navigation/ion-nav.ts new file mode 100644 index 00000000000..c3dabc41d08 --- /dev/null +++ b/packages/angular/src/directives/navigation/ion-nav.ts @@ -0,0 +1,29 @@ +import { + ElementRef, + Injector, + EnvironmentInjector, + NgZone, + ChangeDetectorRef, + Component, + ChangeDetectionStrategy, +} from '@angular/core'; +import { IonNav as IonNavBase, AngularDelegate } from '@ionic/angular/common'; + +@Component({ + selector: 'ion-nav', + template: '', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +// eslint-disable-next-line @angular-eslint/directive-class-suffix +export class IonNav extends IonNavBase { + constructor( + ref: ElementRef, + environmentInjector: EnvironmentInjector, + injector: Injector, + angularDelegate: AngularDelegate, + z: NgZone, + c: ChangeDetectorRef + ) { + super(ref, environmentInjector, injector, angularDelegate, z, c); + } +} diff --git a/packages/angular/src/directives/navigation/nav-delegate.ts b/packages/angular/src/directives/navigation/nav-delegate.ts deleted file mode 100644 index 9d61cd5d655..00000000000 --- a/packages/angular/src/directives/navigation/nav-delegate.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Directive, ElementRef, Injector, EnvironmentInjector, NgZone } from '@angular/core'; -import { NavDelegate as NavDelegateBase, AngularDelegate } from '@ionic/angular/common'; - -@Directive({ - selector: 'ion-nav', -}) -// eslint-disable-next-line @angular-eslint/directive-class-suffix -export class NavDelegate extends NavDelegateBase { - constructor( - ref: ElementRef, - environmentInjector: EnvironmentInjector, - injector: Injector, - angularDelegate: AngularDelegate, - z: NgZone - ) { - super(ref, environmentInjector, injector, angularDelegate, z); - } -} diff --git a/packages/angular/src/directives/proxies-list.ts b/packages/angular/src/directives/proxies-list.ts index 3eee90cbe8f..a1d93f8d588 100644 --- a/packages/angular/src/directives/proxies-list.ts +++ b/packages/angular/src/directives/proxies-list.ts @@ -8,7 +8,6 @@ export const DIRECTIVES = [ d.IonAlert, d.IonApp, d.IonAvatar, - d.IonBackButton, d.IonBackdrop, d.IonBadge, d.IonBreadcrumb, @@ -50,7 +49,6 @@ export const DIRECTIVES = [ d.IonMenu, d.IonMenuButton, d.IonMenuToggle, - d.IonNav, d.IonNavLink, d.IonNote, d.IonPicker, diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts index 7429a4cc60d..1ac643ce19b 100644 --- a/packages/angular/src/directives/proxies.ts +++ b/packages/angular/src/directives/proxies.ts @@ -230,28 +230,6 @@ export class IonAvatar { export declare interface IonAvatar extends Components.IonAvatar {} -@ProxyCmp({ - inputs: ['color', 'defaultHref', 'disabled', 'icon', 'mode', 'routerAnimation', 'text', 'type'] -}) -@Component({ - selector: 'ion-back-button', - changeDetection: ChangeDetectionStrategy.OnPush, - template: '', - // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'defaultHref', 'disabled', 'icon', 'mode', 'routerAnimation', 'text', 'type'], -}) -export class IonBackButton { - protected el: HTMLElement; - constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { - c.detach(); - this.el = r.nativeElement; - } -} - - -export declare interface IonBackButton extends Components.IonBackButton {} - - @ProxyCmp({ inputs: ['stopPropagation', 'tappable', 'visible'] }) @@ -1395,39 +1373,6 @@ export class IonMenuToggle { export declare interface IonMenuToggle extends Components.IonMenuToggle {} -@ProxyCmp({ - inputs: ['animated', 'animation', 'root', 'rootParams', 'swipeGesture'], - methods: ['push', 'insert', 'insertPages', 'pop', 'popTo', 'popToRoot', 'removeIndex', 'setRoot', 'setPages', 'getActive', 'getByIndex', 'canGoBack', 'getPrevious'] -}) -@Component({ - selector: 'ion-nav', - changeDetection: ChangeDetectionStrategy.OnPush, - template: '', - // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'animation', 'root', 'rootParams', 'swipeGesture'], -}) -export class IonNav { - protected el: HTMLElement; - constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { - c.detach(); - this.el = r.nativeElement; - proxyOutputs(this, this.el, ['ionNavWillChange', 'ionNavDidChange']); - } -} - - -export declare interface IonNav extends Components.IonNav { - /** - * Event fired when the nav will change components - */ - ionNavWillChange: EventEmitter>; - /** - * Event fired when the nav has changed components - */ - ionNavDidChange: EventEmitter>; -} - - @ProxyCmp({ inputs: ['component', 'componentProps', 'routerAnimation', 'routerDirection'] }) diff --git a/packages/angular/src/index.ts b/packages/angular/src/index.ts index b90483fa623..e4ce311aad6 100644 --- a/packages/angular/src/index.ts +++ b/packages/angular/src/index.ts @@ -5,8 +5,8 @@ export { RadioValueAccessorDirective as RadioValueAccessor } from './directives/ export { SelectValueAccessorDirective as SelectValueAccessor } from './directives/control-value-accessors/select-value-accessor'; export { TextValueAccessorDirective as TextValueAccessor } from './directives/control-value-accessors/text-value-accessor'; export { IonTabs } from './directives/navigation/ion-tabs'; -export { IonBackButtonDelegateDirective as IonBackButtonDelegate } from './directives/navigation/ion-back-button'; -export { NavDelegate } from './directives/navigation/nav-delegate'; +export { IonBackButton } from './directives/navigation/ion-back-button'; +export { IonNav } from './directives/navigation/ion-nav'; export { IonRouterOutlet } from './directives/navigation/ion-router-outlet'; export { RouterLinkDelegateDirective as RouterLinkDelegate, diff --git a/packages/angular/src/ionic-module.ts b/packages/angular/src/ionic-module.ts index 251d78f591e..285e14fe218 100644 --- a/packages/angular/src/ionic-module.ts +++ b/packages/angular/src/ionic-module.ts @@ -17,10 +17,10 @@ import { SelectValueAccessorDirective, TextValueAccessorDirective, } from './directives/control-value-accessors'; -import { IonBackButtonDelegateDirective } from './directives/navigation/ion-back-button'; +import { IonBackButton } from './directives/navigation/ion-back-button'; +import { IonNav } from './directives/navigation/ion-nav'; import { IonRouterOutlet } from './directives/navigation/ion-router-outlet'; import { IonTabs } from './directives/navigation/ion-tabs'; -import { NavDelegate } from './directives/navigation/nav-delegate'; import { RouterLinkDelegateDirective, RouterLinkWithHrefDelegateDirective, @@ -47,8 +47,8 @@ const DECLARATIONS = [ // navigation IonTabs, IonRouterOutlet, - IonBackButtonDelegateDirective, - NavDelegate, + IonBackButton, + IonNav, RouterLinkDelegateDirective, RouterLinkWithHrefDelegateDirective, ]; diff --git a/packages/angular/standalone/src/directives/angular-component-lib/utils.ts b/packages/angular/standalone/src/directives/angular-component-lib/utils.ts new file mode 100644 index 00000000000..58ed93b7d3c --- /dev/null +++ b/packages/angular/standalone/src/directives/angular-component-lib/utils.ts @@ -0,0 +1,57 @@ +/* eslint-disable */ +/* tslint:disable */ +import { fromEvent } from 'rxjs'; + +export const proxyInputs = (Cmp: any, inputs: string[]) => { + const Prototype = Cmp.prototype; + inputs.forEach((item) => { + Object.defineProperty(Prototype, item, { + get() { + return this.el[item]; + }, + set(val: any) { + this.z.runOutsideAngular(() => (this.el[item] = val)); + }, + }); + }); +}; + +export const proxyMethods = (Cmp: any, methods: string[]) => { + const Prototype = Cmp.prototype; + methods.forEach((methodName) => { + Prototype[methodName] = function () { + const args = arguments; + return this.z.runOutsideAngular(() => this.el[methodName].apply(this.el, args)); + }; + }); +}; + +export const proxyOutputs = (instance: any, el: any, events: string[]) => { + events.forEach((eventName) => (instance[eventName] = fromEvent(el, eventName))); +}; + +export const defineCustomElement = (tagName: string, customElement: any) => { + if (customElement !== undefined && typeof customElements !== 'undefined' && !customElements.get(tagName)) { + customElements.define(tagName, customElement); + } +}; + +// tslint:disable-next-line: only-arrow-functions +export function ProxyCmp(opts: { defineCustomElementFn?: () => void; inputs?: any; methods?: any }) { + const decorator = function (cls: any) { + const { defineCustomElementFn, inputs, methods } = opts; + + if (defineCustomElementFn !== undefined) { + defineCustomElementFn(); + } + + if (inputs) { + proxyInputs(cls, inputs); + } + if (methods) { + proxyMethods(cls, methods); + } + return cls; + }; + return decorator; +} diff --git a/packages/angular/standalone/src/directives/icon.ts b/packages/angular/standalone/src/directives/icon.ts new file mode 100644 index 00000000000..132a3a30b29 --- /dev/null +++ b/packages/angular/standalone/src/directives/icon.ts @@ -0,0 +1,24 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; +import { defineCustomElement as defineIonIcon } from 'ionicons/components/ion-icon.js'; + +import { ProxyCmp } from './angular-component-lib/utils'; + +@ProxyCmp({ + defineCustomElementFn: defineIonIcon, + inputs: ['color', 'flipRtl', 'icon', 'ios', 'lazy', 'md', 'mode', 'name', 'sanitize', 'size', 'src'], +}) +@Component({ + selector: 'ion-icon', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'flipRtl', 'icon', 'ios', 'lazy', 'md', 'mode', 'name', 'sanitize', 'size', 'src'], + standalone: true, +}) +export class IonIcon { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} diff --git a/packages/angular/standalone/src/directives/proxies.ts b/packages/angular/standalone/src/directives/proxies.ts new file mode 100644 index 00000000000..dde743002da --- /dev/null +++ b/packages/angular/standalone/src/directives/proxies.ts @@ -0,0 +1,3159 @@ +/* tslint:disable */ +/* auto-generated angular directive proxies */ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, NgZone } from '@angular/core'; + +import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; + +import type { Components } from '@ionic/core/components'; + +import { defineCustomElement as defineIonAccordion } from '@ionic/core/components/ion-accordion.js'; +import { defineCustomElement as defineIonAccordionGroup } from '@ionic/core/components/ion-accordion-group.js'; +import { defineCustomElement as defineIonActionSheet } from '@ionic/core/components/ion-action-sheet.js'; +import { defineCustomElement as defineIonAlert } from '@ionic/core/components/ion-alert.js'; +import { defineCustomElement as defineIonApp } from '@ionic/core/components/ion-app.js'; +import { defineCustomElement as defineIonAvatar } from '@ionic/core/components/ion-avatar.js'; +import { defineCustomElement as defineIonBackdrop } from '@ionic/core/components/ion-backdrop.js'; +import { defineCustomElement as defineIonBadge } from '@ionic/core/components/ion-badge.js'; +import { defineCustomElement as defineIonBreadcrumb } from '@ionic/core/components/ion-breadcrumb.js'; +import { defineCustomElement as defineIonBreadcrumbs } from '@ionic/core/components/ion-breadcrumbs.js'; +import { defineCustomElement as defineIonButton } from '@ionic/core/components/ion-button.js'; +import { defineCustomElement as defineIonButtons } from '@ionic/core/components/ion-buttons.js'; +import { defineCustomElement as defineIonCard } from '@ionic/core/components/ion-card.js'; +import { defineCustomElement as defineIonCardContent } from '@ionic/core/components/ion-card-content.js'; +import { defineCustomElement as defineIonCardHeader } from '@ionic/core/components/ion-card-header.js'; +import { defineCustomElement as defineIonCardSubtitle } from '@ionic/core/components/ion-card-subtitle.js'; +import { defineCustomElement as defineIonCardTitle } from '@ionic/core/components/ion-card-title.js'; +import { defineCustomElement as defineIonCheckbox } from '@ionic/core/components/ion-checkbox.js'; +import { defineCustomElement as defineIonChip } from '@ionic/core/components/ion-chip.js'; +import { defineCustomElement as defineIonCol } from '@ionic/core/components/ion-col.js'; +import { defineCustomElement as defineIonContent } from '@ionic/core/components/ion-content.js'; +import { defineCustomElement as defineIonDatetime } from '@ionic/core/components/ion-datetime.js'; +import { defineCustomElement as defineIonDatetimeButton } from '@ionic/core/components/ion-datetime-button.js'; +import { defineCustomElement as defineIonFab } from '@ionic/core/components/ion-fab.js'; +import { defineCustomElement as defineIonFabButton } from '@ionic/core/components/ion-fab-button.js'; +import { defineCustomElement as defineIonFabList } from '@ionic/core/components/ion-fab-list.js'; +import { defineCustomElement as defineIonFooter } from '@ionic/core/components/ion-footer.js'; +import { defineCustomElement as defineIonGrid } from '@ionic/core/components/ion-grid.js'; +import { defineCustomElement as defineIonHeader } from '@ionic/core/components/ion-header.js'; +import { defineCustomElement as defineIonImg } from '@ionic/core/components/ion-img.js'; +import { defineCustomElement as defineIonInfiniteScroll } from '@ionic/core/components/ion-infinite-scroll.js'; +import { defineCustomElement as defineIonInfiniteScrollContent } from '@ionic/core/components/ion-infinite-scroll-content.js'; +import { defineCustomElement as defineIonInput } from '@ionic/core/components/ion-input.js'; +import { defineCustomElement as defineIonItem } from '@ionic/core/components/ion-item.js'; +import { defineCustomElement as defineIonItemDivider } from '@ionic/core/components/ion-item-divider.js'; +import { defineCustomElement as defineIonItemGroup } from '@ionic/core/components/ion-item-group.js'; +import { defineCustomElement as defineIonItemOption } from '@ionic/core/components/ion-item-option.js'; +import { defineCustomElement as defineIonItemOptions } from '@ionic/core/components/ion-item-options.js'; +import { defineCustomElement as defineIonItemSliding } from '@ionic/core/components/ion-item-sliding.js'; +import { defineCustomElement as defineIonLabel } from '@ionic/core/components/ion-label.js'; +import { defineCustomElement as defineIonList } from '@ionic/core/components/ion-list.js'; +import { defineCustomElement as defineIonListHeader } from '@ionic/core/components/ion-list-header.js'; +import { defineCustomElement as defineIonLoading } from '@ionic/core/components/ion-loading.js'; +import { defineCustomElement as defineIonMenu } from '@ionic/core/components/ion-menu.js'; +import { defineCustomElement as defineIonMenuButton } from '@ionic/core/components/ion-menu-button.js'; +import { defineCustomElement as defineIonMenuToggle } from '@ionic/core/components/ion-menu-toggle.js'; +import { defineCustomElement as defineIonNavLink } from '@ionic/core/components/ion-nav-link.js'; +import { defineCustomElement as defineIonNote } from '@ionic/core/components/ion-note.js'; +import { defineCustomElement as defineIonPicker } from '@ionic/core/components/ion-picker.js'; +import { defineCustomElement as defineIonProgressBar } from '@ionic/core/components/ion-progress-bar.js'; +import { defineCustomElement as defineIonRadio } from '@ionic/core/components/ion-radio.js'; +import { defineCustomElement as defineIonRadioGroup } from '@ionic/core/components/ion-radio-group.js'; +import { defineCustomElement as defineIonRange } from '@ionic/core/components/ion-range.js'; +import { defineCustomElement as defineIonRefresher } from '@ionic/core/components/ion-refresher.js'; +import { defineCustomElement as defineIonRefresherContent } from '@ionic/core/components/ion-refresher-content.js'; +import { defineCustomElement as defineIonReorder } from '@ionic/core/components/ion-reorder.js'; +import { defineCustomElement as defineIonReorderGroup } from '@ionic/core/components/ion-reorder-group.js'; +import { defineCustomElement as defineIonRippleEffect } from '@ionic/core/components/ion-ripple-effect.js'; +import { defineCustomElement as defineIonRow } from '@ionic/core/components/ion-row.js'; +import { defineCustomElement as defineIonSearchbar } from '@ionic/core/components/ion-searchbar.js'; +import { defineCustomElement as defineIonSegment } from '@ionic/core/components/ion-segment.js'; +import { defineCustomElement as defineIonSegmentButton } from '@ionic/core/components/ion-segment-button.js'; +import { defineCustomElement as defineIonSelect } from '@ionic/core/components/ion-select.js'; +import { defineCustomElement as defineIonSelectOption } from '@ionic/core/components/ion-select-option.js'; +import { defineCustomElement as defineIonSkeletonText } from '@ionic/core/components/ion-skeleton-text.js'; +import { defineCustomElement as defineIonSpinner } from '@ionic/core/components/ion-spinner.js'; +import { defineCustomElement as defineIonSplitPane } from '@ionic/core/components/ion-split-pane.js'; +import { defineCustomElement as defineIonTabBar } from '@ionic/core/components/ion-tab-bar.js'; +import { defineCustomElement as defineIonTabButton } from '@ionic/core/components/ion-tab-button.js'; +import { defineCustomElement as defineIonText } from '@ionic/core/components/ion-text.js'; +import { defineCustomElement as defineIonTextarea } from '@ionic/core/components/ion-textarea.js'; +import { defineCustomElement as defineIonThumbnail } from '@ionic/core/components/ion-thumbnail.js'; +import { defineCustomElement as defineIonTitle } from '@ionic/core/components/ion-title.js'; +import { defineCustomElement as defineIonToast } from '@ionic/core/components/ion-toast.js'; +import { defineCustomElement as defineIonToggle } from '@ionic/core/components/ion-toggle.js'; +import { defineCustomElement as defineIonToolbar } from '@ionic/core/components/ion-toolbar.js'; +@ProxyCmp({ + defineCustomElementFn: defineIonAccordion, + inputs: ['disabled', 'mode', 'readonly', 'toggleIcon', 'toggleIconSlot', 'value'], +}) +@Component({ + selector: 'ion-accordion', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['disabled', 'mode', 'readonly', 'toggleIcon', 'toggleIconSlot', 'value'], + standalone: true, +}) +export class IonAccordion { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonAccordion extends Components.IonAccordion {} + +@ProxyCmp({ + defineCustomElementFn: defineIonAccordionGroup, + inputs: ['animated', 'disabled', 'expand', 'mode', 'multiple', 'readonly', 'value'], +}) +@Component({ + selector: 'ion-accordion-group', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['animated', 'disabled', 'expand', 'mode', 'multiple', 'readonly', 'value'], + standalone: true, +}) +export class IonAccordionGroup { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionChange']); + } +} + +import type { AccordionGroupChangeEventDetail as IIonAccordionGroupAccordionGroupChangeEventDetail } from '@ionic/core/components'; + +export declare interface IonAccordionGroup extends Components.IonAccordionGroup { + /** + * Emitted when the value property has changed +as a result of a user action such as a click. +This event will not emit when programmatically setting +the value property. + */ + ionChange: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonActionSheet, + inputs: [ + 'animated', + 'backdropDismiss', + 'buttons', + 'cssClass', + 'enterAnimation', + 'header', + 'htmlAttributes', + 'isOpen', + 'keyboardClose', + 'leaveAnimation', + 'mode', + 'subHeader', + 'translucent', + 'trigger', + ], + methods: ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss'], +}) +@Component({ + selector: 'ion-action-sheet', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [ + 'animated', + 'backdropDismiss', + 'buttons', + 'cssClass', + 'enterAnimation', + 'header', + 'htmlAttributes', + 'isOpen', + 'keyboardClose', + 'leaveAnimation', + 'mode', + 'subHeader', + 'translucent', + 'trigger', + ], + standalone: true, +}) +export class IonActionSheet { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, [ + 'ionActionSheetDidPresent', + 'ionActionSheetWillPresent', + 'ionActionSheetWillDismiss', + 'ionActionSheetDidDismiss', + 'didPresent', + 'willPresent', + 'willDismiss', + 'didDismiss', + ]); + } +} + +import type { OverlayEventDetail as IIonActionSheetOverlayEventDetail } from '@ionic/core/components'; + +export declare interface IonActionSheet extends Components.IonActionSheet { + /** + * Emitted after the action sheet has presented. + */ + ionActionSheetDidPresent: EventEmitter>; + /** + * Emitted before the action sheet has presented. + */ + ionActionSheetWillPresent: EventEmitter>; + /** + * Emitted before the action sheet has dismissed. + */ + ionActionSheetWillDismiss: EventEmitter>; + /** + * Emitted after the action sheet has dismissed. + */ + ionActionSheetDidDismiss: EventEmitter>; + /** + * Emitted after the action sheet has presented. +Shorthand for ionActionSheetWillDismiss. + */ + didPresent: EventEmitter>; + /** + * Emitted before the action sheet has presented. +Shorthand for ionActionSheetWillPresent. + */ + willPresent: EventEmitter>; + /** + * Emitted before the action sheet has dismissed. +Shorthand for ionActionSheetWillDismiss. + */ + willDismiss: EventEmitter>; + /** + * Emitted after the action sheet has dismissed. +Shorthand for ionActionSheetDidDismiss. + */ + didDismiss: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonAlert, + inputs: [ + 'animated', + 'backdropDismiss', + 'buttons', + 'cssClass', + 'enterAnimation', + 'header', + 'htmlAttributes', + 'inputs', + 'isOpen', + 'keyboardClose', + 'leaveAnimation', + 'message', + 'mode', + 'subHeader', + 'translucent', + 'trigger', + ], + methods: ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss'], +}) +@Component({ + selector: 'ion-alert', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [ + 'animated', + 'backdropDismiss', + 'buttons', + 'cssClass', + 'enterAnimation', + 'header', + 'htmlAttributes', + 'inputs', + 'isOpen', + 'keyboardClose', + 'leaveAnimation', + 'message', + 'mode', + 'subHeader', + 'translucent', + 'trigger', + ], + standalone: true, +}) +export class IonAlert { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, [ + 'ionAlertDidPresent', + 'ionAlertWillPresent', + 'ionAlertWillDismiss', + 'ionAlertDidDismiss', + 'didPresent', + 'willPresent', + 'willDismiss', + 'didDismiss', + ]); + } +} + +import type { OverlayEventDetail as IIonAlertOverlayEventDetail } from '@ionic/core/components'; + +export declare interface IonAlert extends Components.IonAlert { + /** + * Emitted after the alert has presented. + */ + ionAlertDidPresent: EventEmitter>; + /** + * Emitted before the alert has presented. + */ + ionAlertWillPresent: EventEmitter>; + /** + * Emitted before the alert has dismissed. + */ + ionAlertWillDismiss: EventEmitter>; + /** + * Emitted after the alert has dismissed. + */ + ionAlertDidDismiss: EventEmitter>; + /** + * Emitted after the alert has presented. +Shorthand for ionAlertWillDismiss. + */ + didPresent: EventEmitter>; + /** + * Emitted before the alert has presented. +Shorthand for ionAlertWillPresent. + */ + willPresent: EventEmitter>; + /** + * Emitted before the alert has dismissed. +Shorthand for ionAlertWillDismiss. + */ + willDismiss: EventEmitter>; + /** + * Emitted after the alert has dismissed. +Shorthand for ionAlertDidDismiss. + */ + didDismiss: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonApp, +}) +@Component({ + selector: 'ion-app', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [], + standalone: true, +}) +export class IonApp { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonApp extends Components.IonApp {} + +@ProxyCmp({ + defineCustomElementFn: defineIonAvatar, +}) +@Component({ + selector: 'ion-avatar', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [], + standalone: true, +}) +export class IonAvatar { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonAvatar extends Components.IonAvatar {} + +@ProxyCmp({ + defineCustomElementFn: defineIonBackdrop, + inputs: ['stopPropagation', 'tappable', 'visible'], +}) +@Component({ + selector: 'ion-backdrop', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['stopPropagation', 'tappable', 'visible'], + standalone: true, +}) +export class IonBackdrop { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionBackdropTap']); + } +} + +export declare interface IonBackdrop extends Components.IonBackdrop { + /** + * Emitted when the backdrop is tapped. + */ + ionBackdropTap: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonBadge, + inputs: ['color', 'mode'], +}) +@Component({ + selector: 'ion-badge', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'mode'], + standalone: true, +}) +export class IonBadge { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonBadge extends Components.IonBadge {} + +@ProxyCmp({ + defineCustomElementFn: defineIonBreadcrumb, + inputs: [ + 'active', + 'color', + 'disabled', + 'download', + 'href', + 'mode', + 'rel', + 'routerAnimation', + 'routerDirection', + 'separator', + 'target', + ], +}) +@Component({ + selector: 'ion-breadcrumb', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [ + 'active', + 'color', + 'disabled', + 'download', + 'href', + 'mode', + 'rel', + 'routerAnimation', + 'routerDirection', + 'separator', + 'target', + ], + standalone: true, +}) +export class IonBreadcrumb { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionFocus', 'ionBlur']); + } +} + +export declare interface IonBreadcrumb extends Components.IonBreadcrumb { + /** + * Emitted when the breadcrumb has focus. + */ + ionFocus: EventEmitter>; + /** + * Emitted when the breadcrumb loses focus. + */ + ionBlur: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonBreadcrumbs, + inputs: ['color', 'itemsAfterCollapse', 'itemsBeforeCollapse', 'maxItems', 'mode'], +}) +@Component({ + selector: 'ion-breadcrumbs', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'itemsAfterCollapse', 'itemsBeforeCollapse', 'maxItems', 'mode'], + standalone: true, +}) +export class IonBreadcrumbs { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionCollapsedClick']); + } +} + +import type { BreadcrumbCollapsedClickEventDetail as IIonBreadcrumbsBreadcrumbCollapsedClickEventDetail } from '@ionic/core/components'; + +export declare interface IonBreadcrumbs extends Components.IonBreadcrumbs { + /** + * Emitted when the collapsed indicator is clicked on. + */ + ionCollapsedClick: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonButton, + inputs: [ + 'buttonType', + 'color', + 'disabled', + 'download', + 'expand', + 'fill', + 'form', + 'href', + 'mode', + 'rel', + 'routerAnimation', + 'routerDirection', + 'shape', + 'size', + 'strong', + 'target', + 'type', + ], +}) +@Component({ + selector: 'ion-button', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [ + 'buttonType', + 'color', + 'disabled', + 'download', + 'expand', + 'fill', + 'form', + 'href', + 'mode', + 'rel', + 'routerAnimation', + 'routerDirection', + 'shape', + 'size', + 'strong', + 'target', + 'type', + ], + standalone: true, +}) +export class IonButton { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionFocus', 'ionBlur']); + } +} + +export declare interface IonButton extends Components.IonButton { + /** + * Emitted when the button has focus. + */ + ionFocus: EventEmitter>; + /** + * Emitted when the button loses focus. + */ + ionBlur: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonButtons, + inputs: ['collapse'], +}) +@Component({ + selector: 'ion-buttons', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['collapse'], + standalone: true, +}) +export class IonButtons { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonButtons extends Components.IonButtons {} + +@ProxyCmp({ + defineCustomElementFn: defineIonCard, + inputs: [ + 'button', + 'color', + 'disabled', + 'download', + 'href', + 'mode', + 'rel', + 'routerAnimation', + 'routerDirection', + 'target', + 'type', + ], +}) +@Component({ + selector: 'ion-card', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [ + 'button', + 'color', + 'disabled', + 'download', + 'href', + 'mode', + 'rel', + 'routerAnimation', + 'routerDirection', + 'target', + 'type', + ], + standalone: true, +}) +export class IonCard { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonCard extends Components.IonCard {} + +@ProxyCmp({ + defineCustomElementFn: defineIonCardContent, + inputs: ['mode'], +}) +@Component({ + selector: 'ion-card-content', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['mode'], + standalone: true, +}) +export class IonCardContent { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonCardContent extends Components.IonCardContent {} + +@ProxyCmp({ + defineCustomElementFn: defineIonCardHeader, + inputs: ['color', 'mode', 'translucent'], +}) +@Component({ + selector: 'ion-card-header', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'mode', 'translucent'], + standalone: true, +}) +export class IonCardHeader { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonCardHeader extends Components.IonCardHeader {} + +@ProxyCmp({ + defineCustomElementFn: defineIonCardSubtitle, + inputs: ['color', 'mode'], +}) +@Component({ + selector: 'ion-card-subtitle', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'mode'], + standalone: true, +}) +export class IonCardSubtitle { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonCardSubtitle extends Components.IonCardSubtitle {} + +@ProxyCmp({ + defineCustomElementFn: defineIonCardTitle, + inputs: ['color', 'mode'], +}) +@Component({ + selector: 'ion-card-title', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'mode'], + standalone: true, +}) +export class IonCardTitle { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonCardTitle extends Components.IonCardTitle {} + +@ProxyCmp({ + defineCustomElementFn: defineIonCheckbox, + inputs: [ + 'checked', + 'color', + 'disabled', + 'indeterminate', + 'justify', + 'labelPlacement', + 'legacy', + 'mode', + 'name', + 'value', + ], +}) +@Component({ + selector: 'ion-checkbox', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [ + 'checked', + 'color', + 'disabled', + 'indeterminate', + 'justify', + 'labelPlacement', + 'legacy', + 'mode', + 'name', + 'value', + ], + standalone: true, +}) +export class IonCheckbox { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionChange', 'ionFocus', 'ionBlur']); + } +} + +import type { CheckboxChangeEventDetail as IIonCheckboxCheckboxChangeEventDetail } from '@ionic/core/components'; + +export declare interface IonCheckbox extends Components.IonCheckbox { + /** + * Emitted when the checked property has changed +as a result of a user action such as a click. +This event will not emit when programmatically +setting the checked property. + */ + ionChange: EventEmitter>; + /** + * Emitted when the checkbox has focus. + */ + ionFocus: EventEmitter>; + /** + * Emitted when the checkbox loses focus. + */ + ionBlur: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonChip, + inputs: ['color', 'disabled', 'mode', 'outline'], +}) +@Component({ + selector: 'ion-chip', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'disabled', 'mode', 'outline'], + standalone: true, +}) +export class IonChip { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonChip extends Components.IonChip {} + +@ProxyCmp({ + defineCustomElementFn: defineIonCol, + inputs: [ + 'offset', + 'offsetLg', + 'offsetMd', + 'offsetSm', + 'offsetXl', + 'offsetXs', + 'pull', + 'pullLg', + 'pullMd', + 'pullSm', + 'pullXl', + 'pullXs', + 'push', + 'pushLg', + 'pushMd', + 'pushSm', + 'pushXl', + 'pushXs', + 'size', + 'sizeLg', + 'sizeMd', + 'sizeSm', + 'sizeXl', + 'sizeXs', + ], +}) +@Component({ + selector: 'ion-col', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [ + 'offset', + 'offsetLg', + 'offsetMd', + 'offsetSm', + 'offsetXl', + 'offsetXs', + 'pull', + 'pullLg', + 'pullMd', + 'pullSm', + 'pullXl', + 'pullXs', + 'push', + 'pushLg', + 'pushMd', + 'pushSm', + 'pushXl', + 'pushXs', + 'size', + 'sizeLg', + 'sizeMd', + 'sizeSm', + 'sizeXl', + 'sizeXs', + ], + standalone: true, +}) +export class IonCol { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonCol extends Components.IonCol {} + +@ProxyCmp({ + defineCustomElementFn: defineIonContent, + inputs: ['color', 'forceOverscroll', 'fullscreen', 'scrollEvents', 'scrollX', 'scrollY'], + methods: ['getScrollElement', 'scrollToTop', 'scrollToBottom', 'scrollByPoint', 'scrollToPoint'], +}) +@Component({ + selector: 'ion-content', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'forceOverscroll', 'fullscreen', 'scrollEvents', 'scrollX', 'scrollY'], + standalone: true, +}) +export class IonContent { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionScrollStart', 'ionScroll', 'ionScrollEnd']); + } +} + +import type { ScrollBaseDetail as IIonContentScrollBaseDetail } from '@ionic/core/components'; +import type { ScrollDetail as IIonContentScrollDetail } from '@ionic/core/components'; + +export declare interface IonContent extends Components.IonContent { + /** + * Emitted when the scroll has started. This event is disabled by default. +Set `scrollEvents` to `true` to enable. + */ + ionScrollStart: EventEmitter>; + /** + * Emitted while scrolling. This event is disabled by default. +Set `scrollEvents` to `true` to enable. + */ + ionScroll: EventEmitter>; + /** + * Emitted when the scroll has ended. This event is disabled by default. +Set `scrollEvents` to `true` to enable. + */ + ionScrollEnd: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonDatetime, + inputs: [ + 'cancelText', + 'clearText', + 'color', + 'dayValues', + 'disabled', + 'doneText', + 'firstDayOfWeek', + 'highlightedDates', + 'hourCycle', + 'hourValues', + 'isDateEnabled', + 'locale', + 'max', + 'min', + 'minuteValues', + 'mode', + 'monthValues', + 'multiple', + 'name', + 'preferWheel', + 'presentation', + 'readonly', + 'showClearButton', + 'showDefaultButtons', + 'showDefaultTimeLabel', + 'showDefaultTitle', + 'size', + 'titleSelectedDatesFormatter', + 'value', + 'yearValues', + ], + methods: ['confirm', 'reset', 'cancel'], +}) +@Component({ + selector: 'ion-datetime', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [ + 'cancelText', + 'clearText', + 'color', + 'dayValues', + 'disabled', + 'doneText', + 'firstDayOfWeek', + 'highlightedDates', + 'hourCycle', + 'hourValues', + 'isDateEnabled', + 'locale', + 'max', + 'min', + 'minuteValues', + 'mode', + 'monthValues', + 'multiple', + 'name', + 'preferWheel', + 'presentation', + 'readonly', + 'showClearButton', + 'showDefaultButtons', + 'showDefaultTimeLabel', + 'showDefaultTitle', + 'size', + 'titleSelectedDatesFormatter', + 'value', + 'yearValues', + ], + standalone: true, +}) +export class IonDatetime { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionCancel', 'ionChange', 'ionFocus', 'ionBlur']); + } +} + +import type { DatetimeChangeEventDetail as IIonDatetimeDatetimeChangeEventDetail } from '@ionic/core/components'; + +export declare interface IonDatetime extends Components.IonDatetime { + /** + * Emitted when the datetime selection was cancelled. + */ + ionCancel: EventEmitter>; + /** + * Emitted when the value (selected date) has changed. + */ + ionChange: EventEmitter>; + /** + * Emitted when the datetime has focus. + */ + ionFocus: EventEmitter>; + /** + * Emitted when the datetime loses focus. + */ + ionBlur: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonDatetimeButton, + inputs: ['color', 'datetime', 'disabled', 'mode'], +}) +@Component({ + selector: 'ion-datetime-button', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'datetime', 'disabled', 'mode'], + standalone: true, +}) +export class IonDatetimeButton { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonDatetimeButton extends Components.IonDatetimeButton {} + +@ProxyCmp({ + defineCustomElementFn: defineIonFab, + inputs: ['activated', 'edge', 'horizontal', 'vertical'], + methods: ['close'], +}) +@Component({ + selector: 'ion-fab', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['activated', 'edge', 'horizontal', 'vertical'], + standalone: true, +}) +export class IonFab { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonFab extends Components.IonFab {} + +@ProxyCmp({ + defineCustomElementFn: defineIonFabButton, + inputs: [ + 'activated', + 'closeIcon', + 'color', + 'disabled', + 'download', + 'href', + 'mode', + 'rel', + 'routerAnimation', + 'routerDirection', + 'show', + 'size', + 'target', + 'translucent', + 'type', + ], +}) +@Component({ + selector: 'ion-fab-button', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [ + 'activated', + 'closeIcon', + 'color', + 'disabled', + 'download', + 'href', + 'mode', + 'rel', + 'routerAnimation', + 'routerDirection', + 'show', + 'size', + 'target', + 'translucent', + 'type', + ], + standalone: true, +}) +export class IonFabButton { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionFocus', 'ionBlur']); + } +} + +export declare interface IonFabButton extends Components.IonFabButton { + /** + * Emitted when the button has focus. + */ + ionFocus: EventEmitter>; + /** + * Emitted when the button loses focus. + */ + ionBlur: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonFabList, + inputs: ['activated', 'side'], +}) +@Component({ + selector: 'ion-fab-list', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['activated', 'side'], + standalone: true, +}) +export class IonFabList { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonFabList extends Components.IonFabList {} + +@ProxyCmp({ + defineCustomElementFn: defineIonFooter, + inputs: ['collapse', 'mode', 'translucent'], +}) +@Component({ + selector: 'ion-footer', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['collapse', 'mode', 'translucent'], + standalone: true, +}) +export class IonFooter { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonFooter extends Components.IonFooter {} + +@ProxyCmp({ + defineCustomElementFn: defineIonGrid, + inputs: ['fixed'], +}) +@Component({ + selector: 'ion-grid', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['fixed'], + standalone: true, +}) +export class IonGrid { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonGrid extends Components.IonGrid {} + +@ProxyCmp({ + defineCustomElementFn: defineIonHeader, + inputs: ['collapse', 'mode', 'translucent'], +}) +@Component({ + selector: 'ion-header', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['collapse', 'mode', 'translucent'], + standalone: true, +}) +export class IonHeader { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonHeader extends Components.IonHeader {} + +@ProxyCmp({ + defineCustomElementFn: defineIonImg, + inputs: ['alt', 'src'], +}) +@Component({ + selector: 'ion-img', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['alt', 'src'], + standalone: true, +}) +export class IonImg { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionImgWillLoad', 'ionImgDidLoad', 'ionError']); + } +} + +export declare interface IonImg extends Components.IonImg { + /** + * Emitted when the img src has been set + */ + ionImgWillLoad: EventEmitter>; + /** + * Emitted when the image has finished loading + */ + ionImgDidLoad: EventEmitter>; + /** + * Emitted when the img fails to load + */ + ionError: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonInfiniteScroll, + inputs: ['disabled', 'position', 'threshold'], + methods: ['complete'], +}) +@Component({ + selector: 'ion-infinite-scroll', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['disabled', 'position', 'threshold'], + standalone: true, +}) +export class IonInfiniteScroll { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionInfinite']); + } +} + +export declare interface IonInfiniteScroll extends Components.IonInfiniteScroll { + /** + * Emitted when the scroll reaches +the threshold distance. From within your infinite handler, +you must call the infinite scroll's `complete()` method when +your async operation has completed. + */ + ionInfinite: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonInfiniteScrollContent, + inputs: ['loadingSpinner', 'loadingText'], +}) +@Component({ + selector: 'ion-infinite-scroll-content', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['loadingSpinner', 'loadingText'], + standalone: true, +}) +export class IonInfiniteScrollContent { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonInfiniteScrollContent extends Components.IonInfiniteScrollContent {} + +@ProxyCmp({ + defineCustomElementFn: defineIonInput, + inputs: [ + 'accept', + 'autocapitalize', + 'autocomplete', + 'autocorrect', + 'autofocus', + 'clearInput', + 'clearOnEdit', + 'color', + 'counter', + 'counterFormatter', + 'debounce', + 'disabled', + 'enterkeyhint', + 'errorText', + 'fill', + 'helperText', + 'inputmode', + 'label', + 'labelPlacement', + 'legacy', + 'max', + 'maxlength', + 'min', + 'minlength', + 'mode', + 'multiple', + 'name', + 'pattern', + 'placeholder', + 'readonly', + 'required', + 'shape', + 'size', + 'spellcheck', + 'step', + 'type', + 'value', + ], + methods: ['setFocus', 'getInputElement'], +}) +@Component({ + selector: 'ion-input', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [ + 'accept', + 'autocapitalize', + 'autocomplete', + 'autocorrect', + 'autofocus', + 'clearInput', + 'clearOnEdit', + 'color', + 'counter', + 'counterFormatter', + 'debounce', + 'disabled', + 'enterkeyhint', + 'errorText', + 'fill', + 'helperText', + 'inputmode', + 'label', + 'labelPlacement', + 'legacy', + 'max', + 'maxlength', + 'min', + 'minlength', + 'mode', + 'multiple', + 'name', + 'pattern', + 'placeholder', + 'readonly', + 'required', + 'shape', + 'size', + 'spellcheck', + 'step', + 'type', + 'value', + ], + standalone: true, +}) +export class IonInput { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionInput', 'ionChange', 'ionBlur', 'ionFocus']); + } +} + +import type { InputInputEventDetail as IIonInputInputInputEventDetail } from '@ionic/core/components'; +import type { InputChangeEventDetail as IIonInputInputChangeEventDetail } from '@ionic/core/components'; + +export declare interface IonInput extends Components.IonInput { + /** + * The `ionInput` event is fired each time the user modifies the input's value. +Unlike the `ionChange` event, the `ionInput` event is fired for each alteration +to the input's value. This typically happens for each keystroke as the user types. + +For elements that accept text input (`type=text`, `type=tel`, etc.), the interface +is [`InputEvent`](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent); for others, +the interface is [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event). If +the input is cleared on edit, the type is `null`. + */ + ionInput: EventEmitter>; + /** + * The `ionChange` event is fired when the user modifies the input's value. +Unlike the `ionInput` event, the `ionChange` event is only fired when changes +are committed, not as the user types. + +Depending on the way the users interacts with the element, the `ionChange` +event fires at a different moment: +- When the user commits the change explicitly (e.g. by selecting a date +from a date picker for ``, pressing the "Enter" key, etc.). +- When the element loses focus after its value has changed: for elements +where the user's interaction is typing. + */ + ionChange: EventEmitter>; + /** + * Emitted when the input loses focus. + */ + ionBlur: EventEmitter>; + /** + * Emitted when the input has focus. + */ + ionFocus: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonItem, + inputs: [ + 'button', + 'color', + 'counter', + 'counterFormatter', + 'detail', + 'detailIcon', + 'disabled', + 'download', + 'fill', + 'href', + 'lines', + 'mode', + 'rel', + 'routerAnimation', + 'routerDirection', + 'shape', + 'target', + 'type', + ], +}) +@Component({ + selector: 'ion-item', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [ + 'button', + 'color', + 'counter', + 'counterFormatter', + 'detail', + 'detailIcon', + 'disabled', + 'download', + 'fill', + 'href', + 'lines', + 'mode', + 'rel', + 'routerAnimation', + 'routerDirection', + 'shape', + 'target', + 'type', + ], + standalone: true, +}) +export class IonItem { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonItem extends Components.IonItem {} + +@ProxyCmp({ + defineCustomElementFn: defineIonItemDivider, + inputs: ['color', 'mode', 'sticky'], +}) +@Component({ + selector: 'ion-item-divider', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'mode', 'sticky'], + standalone: true, +}) +export class IonItemDivider { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonItemDivider extends Components.IonItemDivider {} + +@ProxyCmp({ + defineCustomElementFn: defineIonItemGroup, +}) +@Component({ + selector: 'ion-item-group', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [], + standalone: true, +}) +export class IonItemGroup { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonItemGroup extends Components.IonItemGroup {} + +@ProxyCmp({ + defineCustomElementFn: defineIonItemOption, + inputs: ['color', 'disabled', 'download', 'expandable', 'href', 'mode', 'rel', 'target', 'type'], +}) +@Component({ + selector: 'ion-item-option', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'disabled', 'download', 'expandable', 'href', 'mode', 'rel', 'target', 'type'], + standalone: true, +}) +export class IonItemOption { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonItemOption extends Components.IonItemOption {} + +@ProxyCmp({ + defineCustomElementFn: defineIonItemOptions, + inputs: ['side'], +}) +@Component({ + selector: 'ion-item-options', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['side'], + standalone: true, +}) +export class IonItemOptions { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionSwipe']); + } +} + +export declare interface IonItemOptions extends Components.IonItemOptions { + /** + * Emitted when the item has been fully swiped. + */ + ionSwipe: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonItemSliding, + inputs: ['disabled'], + methods: ['getOpenAmount', 'getSlidingRatio', 'open', 'close', 'closeOpened'], +}) +@Component({ + selector: 'ion-item-sliding', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['disabled'], + standalone: true, +}) +export class IonItemSliding { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionDrag']); + } +} + +export declare interface IonItemSliding extends Components.IonItemSliding { + /** + * Emitted when the sliding position changes. + */ + ionDrag: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonLabel, + inputs: ['color', 'mode', 'position'], +}) +@Component({ + selector: 'ion-label', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'mode', 'position'], + standalone: true, +}) +export class IonLabel { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonLabel extends Components.IonLabel {} + +@ProxyCmp({ + defineCustomElementFn: defineIonList, + inputs: ['inset', 'lines', 'mode'], + methods: ['closeSlidingItems'], +}) +@Component({ + selector: 'ion-list', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['inset', 'lines', 'mode'], + standalone: true, +}) +export class IonList { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonList extends Components.IonList {} + +@ProxyCmp({ + defineCustomElementFn: defineIonListHeader, + inputs: ['color', 'lines', 'mode'], +}) +@Component({ + selector: 'ion-list-header', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'lines', 'mode'], + standalone: true, +}) +export class IonListHeader { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonListHeader extends Components.IonListHeader {} + +@ProxyCmp({ + defineCustomElementFn: defineIonLoading, + inputs: [ + 'animated', + 'backdropDismiss', + 'cssClass', + 'duration', + 'enterAnimation', + 'htmlAttributes', + 'isOpen', + 'keyboardClose', + 'leaveAnimation', + 'message', + 'mode', + 'showBackdrop', + 'spinner', + 'translucent', + 'trigger', + ], + methods: ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss'], +}) +@Component({ + selector: 'ion-loading', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [ + 'animated', + 'backdropDismiss', + 'cssClass', + 'duration', + 'enterAnimation', + 'htmlAttributes', + 'isOpen', + 'keyboardClose', + 'leaveAnimation', + 'message', + 'mode', + 'showBackdrop', + 'spinner', + 'translucent', + 'trigger', + ], + standalone: true, +}) +export class IonLoading { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, [ + 'ionLoadingDidPresent', + 'ionLoadingWillPresent', + 'ionLoadingWillDismiss', + 'ionLoadingDidDismiss', + 'didPresent', + 'willPresent', + 'willDismiss', + 'didDismiss', + ]); + } +} + +import type { OverlayEventDetail as IIonLoadingOverlayEventDetail } from '@ionic/core/components'; + +export declare interface IonLoading extends Components.IonLoading { + /** + * Emitted after the loading has presented. + */ + ionLoadingDidPresent: EventEmitter>; + /** + * Emitted before the loading has presented. + */ + ionLoadingWillPresent: EventEmitter>; + /** + * Emitted before the loading has dismissed. + */ + ionLoadingWillDismiss: EventEmitter>; + /** + * Emitted after the loading has dismissed. + */ + ionLoadingDidDismiss: EventEmitter>; + /** + * Emitted after the loading indicator has presented. +Shorthand for ionLoadingWillDismiss. + */ + didPresent: EventEmitter>; + /** + * Emitted before the loading indicator has presented. +Shorthand for ionLoadingWillPresent. + */ + willPresent: EventEmitter>; + /** + * Emitted before the loading indicator has dismissed. +Shorthand for ionLoadingWillDismiss. + */ + willDismiss: EventEmitter>; + /** + * Emitted after the loading indicator has dismissed. +Shorthand for ionLoadingDidDismiss. + */ + didDismiss: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonMenu, + inputs: ['contentId', 'disabled', 'maxEdgeStart', 'menuId', 'side', 'swipeGesture', 'type'], + methods: ['isOpen', 'isActive', 'open', 'close', 'toggle', 'setOpen'], +}) +@Component({ + selector: 'ion-menu', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['contentId', 'disabled', 'maxEdgeStart', 'menuId', 'side', 'swipeGesture', 'type'], + standalone: true, +}) +export class IonMenu { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionWillOpen', 'ionWillClose', 'ionDidOpen', 'ionDidClose']); + } +} + +export declare interface IonMenu extends Components.IonMenu { + /** + * Emitted when the menu is about to be opened. + */ + ionWillOpen: EventEmitter>; + /** + * Emitted when the menu is about to be closed. + */ + ionWillClose: EventEmitter>; + /** + * Emitted when the menu is open. + */ + ionDidOpen: EventEmitter>; + /** + * Emitted when the menu is closed. + */ + ionDidClose: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonMenuButton, + inputs: ['autoHide', 'color', 'disabled', 'menu', 'mode', 'type'], +}) +@Component({ + selector: 'ion-menu-button', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['autoHide', 'color', 'disabled', 'menu', 'mode', 'type'], + standalone: true, +}) +export class IonMenuButton { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonMenuButton extends Components.IonMenuButton {} + +@ProxyCmp({ + defineCustomElementFn: defineIonMenuToggle, + inputs: ['autoHide', 'menu'], +}) +@Component({ + selector: 'ion-menu-toggle', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['autoHide', 'menu'], + standalone: true, +}) +export class IonMenuToggle { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonMenuToggle extends Components.IonMenuToggle {} + +@ProxyCmp({ + defineCustomElementFn: defineIonNavLink, + inputs: ['component', 'componentProps', 'routerAnimation', 'routerDirection'], +}) +@Component({ + selector: 'ion-nav-link', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['component', 'componentProps', 'routerAnimation', 'routerDirection'], + standalone: true, +}) +export class IonNavLink { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonNavLink extends Components.IonNavLink {} + +@ProxyCmp({ + defineCustomElementFn: defineIonNote, + inputs: ['color', 'mode'], +}) +@Component({ + selector: 'ion-note', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'mode'], + standalone: true, +}) +export class IonNote { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonNote extends Components.IonNote {} + +@ProxyCmp({ + defineCustomElementFn: defineIonPicker, + inputs: [ + 'animated', + 'backdropDismiss', + 'buttons', + 'columns', + 'cssClass', + 'duration', + 'enterAnimation', + 'htmlAttributes', + 'isOpen', + 'keyboardClose', + 'leaveAnimation', + 'mode', + 'showBackdrop', + 'trigger', + ], + methods: ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss', 'getColumn'], +}) +@Component({ + selector: 'ion-picker', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [ + 'animated', + 'backdropDismiss', + 'buttons', + 'columns', + 'cssClass', + 'duration', + 'enterAnimation', + 'htmlAttributes', + 'isOpen', + 'keyboardClose', + 'leaveAnimation', + 'mode', + 'showBackdrop', + 'trigger', + ], + standalone: true, +}) +export class IonPicker { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, [ + 'ionPickerDidPresent', + 'ionPickerWillPresent', + 'ionPickerWillDismiss', + 'ionPickerDidDismiss', + 'didPresent', + 'willPresent', + 'willDismiss', + 'didDismiss', + ]); + } +} + +import type { OverlayEventDetail as IIonPickerOverlayEventDetail } from '@ionic/core/components'; + +export declare interface IonPicker extends Components.IonPicker { + /** + * Emitted after the picker has presented. + */ + ionPickerDidPresent: EventEmitter>; + /** + * Emitted before the picker has presented. + */ + ionPickerWillPresent: EventEmitter>; + /** + * Emitted before the picker has dismissed. + */ + ionPickerWillDismiss: EventEmitter>; + /** + * Emitted after the picker has dismissed. + */ + ionPickerDidDismiss: EventEmitter>; + /** + * Emitted after the picker has presented. +Shorthand for ionPickerWillDismiss. + */ + didPresent: EventEmitter>; + /** + * Emitted before the picker has presented. +Shorthand for ionPickerWillPresent. + */ + willPresent: EventEmitter>; + /** + * Emitted before the picker has dismissed. +Shorthand for ionPickerWillDismiss. + */ + willDismiss: EventEmitter>; + /** + * Emitted after the picker has dismissed. +Shorthand for ionPickerDidDismiss. + */ + didDismiss: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonProgressBar, + inputs: ['buffer', 'color', 'mode', 'reversed', 'type', 'value'], +}) +@Component({ + selector: 'ion-progress-bar', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['buffer', 'color', 'mode', 'reversed', 'type', 'value'], + standalone: true, +}) +export class IonProgressBar { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonProgressBar extends Components.IonProgressBar {} + +@ProxyCmp({ + defineCustomElementFn: defineIonRadio, + inputs: ['color', 'disabled', 'justify', 'labelPlacement', 'legacy', 'mode', 'name', 'value'], +}) +@Component({ + selector: 'ion-radio', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'disabled', 'justify', 'labelPlacement', 'legacy', 'mode', 'name', 'value'], + standalone: true, +}) +export class IonRadio { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionFocus', 'ionBlur']); + } +} + +export declare interface IonRadio extends Components.IonRadio { + /** + * Emitted when the radio button has focus. + */ + ionFocus: EventEmitter>; + /** + * Emitted when the radio button loses focus. + */ + ionBlur: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonRadioGroup, + inputs: ['allowEmptySelection', 'name', 'value'], +}) +@Component({ + selector: 'ion-radio-group', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['allowEmptySelection', 'name', 'value'], + standalone: true, +}) +export class IonRadioGroup { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionChange']); + } +} + +import type { RadioGroupChangeEventDetail as IIonRadioGroupRadioGroupChangeEventDetail } from '@ionic/core/components'; + +export declare interface IonRadioGroup extends Components.IonRadioGroup { + /** + * Emitted when the value has changed. + */ + ionChange: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonRange, + inputs: [ + 'activeBarStart', + 'color', + 'debounce', + 'disabled', + 'dualKnobs', + 'label', + 'labelPlacement', + 'legacy', + 'max', + 'min', + 'mode', + 'name', + 'pin', + 'pinFormatter', + 'snaps', + 'step', + 'ticks', + 'value', + ], +}) +@Component({ + selector: 'ion-range', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [ + 'activeBarStart', + 'color', + 'debounce', + 'disabled', + 'dualKnobs', + 'label', + 'labelPlacement', + 'legacy', + 'max', + 'min', + 'mode', + 'name', + 'pin', + 'pinFormatter', + 'snaps', + 'step', + 'ticks', + 'value', + ], + standalone: true, +}) +export class IonRange { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionChange', 'ionInput', 'ionFocus', 'ionBlur', 'ionKnobMoveStart', 'ionKnobMoveEnd']); + } +} + +import type { RangeChangeEventDetail as IIonRangeRangeChangeEventDetail } from '@ionic/core/components'; +import type { RangeKnobMoveStartEventDetail as IIonRangeRangeKnobMoveStartEventDetail } from '@ionic/core/components'; +import type { RangeKnobMoveEndEventDetail as IIonRangeRangeKnobMoveEndEventDetail } from '@ionic/core/components'; + +export declare interface IonRange extends Components.IonRange { + /** + * The `ionChange` event is fired for `` elements when the user +modifies the element's value: +- When the user releases the knob after dragging; +- When the user moves the knob with keyboard arrows + +`ionChange` is not fired when the value is changed programmatically. + */ + ionChange: EventEmitter>; + /** + * The `ionInput` event is fired for `` elements when the value +is modified. Unlike `ionChange`, `ionInput` is fired continuously +while the user is dragging the knob. + */ + ionInput: EventEmitter>; + /** + * Emitted when the range has focus. + */ + ionFocus: EventEmitter>; + /** + * Emitted when the range loses focus. + */ + ionBlur: EventEmitter>; + /** + * Emitted when the user starts moving the range knob, whether through +mouse drag, touch gesture, or keyboard interaction. + */ + ionKnobMoveStart: EventEmitter>; + /** + * Emitted when the user finishes moving the range knob, whether through +mouse drag, touch gesture, or keyboard interaction. + */ + ionKnobMoveEnd: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonRefresher, + inputs: ['closeDuration', 'disabled', 'pullFactor', 'pullMax', 'pullMin', 'snapbackDuration'], + methods: ['complete', 'cancel', 'getProgress'], +}) +@Component({ + selector: 'ion-refresher', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['closeDuration', 'disabled', 'pullFactor', 'pullMax', 'pullMin', 'snapbackDuration'], + standalone: true, +}) +export class IonRefresher { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionRefresh', 'ionPull', 'ionStart']); + } +} + +import type { RefresherEventDetail as IIonRefresherRefresherEventDetail } from '@ionic/core/components'; + +export declare interface IonRefresher extends Components.IonRefresher { + /** + * Emitted when the user lets go of the content and has pulled down +further than the `pullMin` or pulls the content down and exceeds the pullMax. +Updates the refresher state to `refreshing`. The `complete()` method should be +called when the async operation has completed. + */ + ionRefresh: EventEmitter>; + /** + * Emitted while the user is pulling down the content and exposing the refresher. + */ + ionPull: EventEmitter>; + /** + * Emitted when the user begins to start pulling down. + */ + ionStart: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonRefresherContent, + inputs: ['pullingIcon', 'pullingText', 'refreshingSpinner', 'refreshingText'], +}) +@Component({ + selector: 'ion-refresher-content', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['pullingIcon', 'pullingText', 'refreshingSpinner', 'refreshingText'], + standalone: true, +}) +export class IonRefresherContent { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonRefresherContent extends Components.IonRefresherContent {} + +@ProxyCmp({ + defineCustomElementFn: defineIonReorder, +}) +@Component({ + selector: 'ion-reorder', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [], + standalone: true, +}) +export class IonReorder { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonReorder extends Components.IonReorder {} + +@ProxyCmp({ + defineCustomElementFn: defineIonReorderGroup, + inputs: ['disabled'], + methods: ['complete'], +}) +@Component({ + selector: 'ion-reorder-group', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['disabled'], + standalone: true, +}) +export class IonReorderGroup { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionItemReorder']); + } +} + +import type { ItemReorderEventDetail as IIonReorderGroupItemReorderEventDetail } from '@ionic/core/components'; + +export declare interface IonReorderGroup extends Components.IonReorderGroup { + /** + * Event that needs to be listened to in order to complete the reorder action. +Once the event has been emitted, the `complete()` method then needs +to be called in order to finalize the reorder action. + */ + ionItemReorder: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonRippleEffect, + inputs: ['type'], + methods: ['addRipple'], +}) +@Component({ + selector: 'ion-ripple-effect', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['type'], + standalone: true, +}) +export class IonRippleEffect { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonRippleEffect extends Components.IonRippleEffect {} + +@ProxyCmp({ + defineCustomElementFn: defineIonRow, +}) +@Component({ + selector: 'ion-row', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [], + standalone: true, +}) +export class IonRow { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonRow extends Components.IonRow {} + +@ProxyCmp({ + defineCustomElementFn: defineIonSearchbar, + inputs: [ + 'animated', + 'autocomplete', + 'autocorrect', + 'cancelButtonIcon', + 'cancelButtonText', + 'clearIcon', + 'color', + 'debounce', + 'disabled', + 'enterkeyhint', + 'inputmode', + 'mode', + 'name', + 'placeholder', + 'searchIcon', + 'showCancelButton', + 'showClearButton', + 'spellcheck', + 'type', + 'value', + ], + methods: ['setFocus', 'getInputElement'], +}) +@Component({ + selector: 'ion-searchbar', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [ + 'animated', + 'autocomplete', + 'autocorrect', + 'cancelButtonIcon', + 'cancelButtonText', + 'clearIcon', + 'color', + 'debounce', + 'disabled', + 'enterkeyhint', + 'inputmode', + 'mode', + 'name', + 'placeholder', + 'searchIcon', + 'showCancelButton', + 'showClearButton', + 'spellcheck', + 'type', + 'value', + ], + standalone: true, +}) +export class IonSearchbar { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionInput', 'ionChange', 'ionCancel', 'ionClear', 'ionBlur', 'ionFocus']); + } +} + +import type { SearchbarInputEventDetail as IIonSearchbarSearchbarInputEventDetail } from '@ionic/core/components'; +import type { SearchbarChangeEventDetail as IIonSearchbarSearchbarChangeEventDetail } from '@ionic/core/components'; + +export declare interface IonSearchbar extends Components.IonSearchbar { + /** + * Emitted when the `value` of the `ion-searchbar` element has changed. + */ + ionInput: EventEmitter>; + /** + * The `ionChange` event is fired for `` elements when the user +modifies the element's value. Unlike the `ionInput` event, the `ionChange` +event is not necessarily fired for each alteration to an element's value. + +The `ionChange` event is fired when the value has been committed +by the user. This can happen when the element loses focus or +when the "Enter" key is pressed. `ionChange` can also fire +when clicking the clear or cancel buttons. + */ + ionChange: EventEmitter>; + /** + * Emitted when the cancel button is clicked. + */ + ionCancel: EventEmitter>; + /** + * Emitted when the clear input button is clicked. + */ + ionClear: EventEmitter>; + /** + * Emitted when the input loses focus. + */ + ionBlur: EventEmitter>; + /** + * Emitted when the input has focus. + */ + ionFocus: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonSegment, + inputs: ['color', 'disabled', 'mode', 'scrollable', 'selectOnFocus', 'swipeGesture', 'value'], +}) +@Component({ + selector: 'ion-segment', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'disabled', 'mode', 'scrollable', 'selectOnFocus', 'swipeGesture', 'value'], + standalone: true, +}) +export class IonSegment { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionChange']); + } +} + +import type { SegmentChangeEventDetail as IIonSegmentSegmentChangeEventDetail } from '@ionic/core/components'; + +export declare interface IonSegment extends Components.IonSegment { + /** + * Emitted when the value property has changed and any +dragging pointer has been released from `ion-segment`. + */ + ionChange: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonSegmentButton, + inputs: ['disabled', 'layout', 'mode', 'type', 'value'], +}) +@Component({ + selector: 'ion-segment-button', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['disabled', 'layout', 'mode', 'type', 'value'], + standalone: true, +}) +export class IonSegmentButton { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonSegmentButton extends Components.IonSegmentButton {} + +@ProxyCmp({ + defineCustomElementFn: defineIonSelect, + inputs: [ + 'cancelText', + 'color', + 'compareWith', + 'disabled', + 'expandedIcon', + 'fill', + 'interface', + 'interfaceOptions', + 'justify', + 'label', + 'labelPlacement', + 'legacy', + 'mode', + 'multiple', + 'name', + 'okText', + 'placeholder', + 'selectedText', + 'shape', + 'toggleIcon', + 'value', + ], + methods: ['open'], +}) +@Component({ + selector: 'ion-select', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [ + 'cancelText', + 'color', + 'compareWith', + 'disabled', + 'expandedIcon', + 'fill', + 'interface', + 'interfaceOptions', + 'justify', + 'label', + 'labelPlacement', + 'legacy', + 'mode', + 'multiple', + 'name', + 'okText', + 'placeholder', + 'selectedText', + 'shape', + 'toggleIcon', + 'value', + ], + standalone: true, +}) +export class IonSelect { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionChange', 'ionCancel', 'ionDismiss', 'ionFocus', 'ionBlur']); + } +} + +import type { SelectChangeEventDetail as IIonSelectSelectChangeEventDetail } from '@ionic/core/components'; + +export declare interface IonSelect extends Components.IonSelect { + /** + * Emitted when the value has changed. + */ + ionChange: EventEmitter>; + /** + * Emitted when the selection is cancelled. + */ + ionCancel: EventEmitter>; + /** + * Emitted when the overlay is dismissed. + */ + ionDismiss: EventEmitter>; + /** + * Emitted when the select has focus. + */ + ionFocus: EventEmitter>; + /** + * Emitted when the select loses focus. + */ + ionBlur: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonSelectOption, + inputs: ['disabled', 'value'], +}) +@Component({ + selector: 'ion-select-option', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['disabled', 'value'], + standalone: true, +}) +export class IonSelectOption { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonSelectOption extends Components.IonSelectOption {} + +@ProxyCmp({ + defineCustomElementFn: defineIonSkeletonText, + inputs: ['animated'], +}) +@Component({ + selector: 'ion-skeleton-text', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['animated'], + standalone: true, +}) +export class IonSkeletonText { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonSkeletonText extends Components.IonSkeletonText {} + +@ProxyCmp({ + defineCustomElementFn: defineIonSpinner, + inputs: ['color', 'duration', 'name', 'paused'], +}) +@Component({ + selector: 'ion-spinner', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'duration', 'name', 'paused'], + standalone: true, +}) +export class IonSpinner { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonSpinner extends Components.IonSpinner {} + +@ProxyCmp({ + defineCustomElementFn: defineIonSplitPane, + inputs: ['contentId', 'disabled', 'when'], +}) +@Component({ + selector: 'ion-split-pane', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['contentId', 'disabled', 'when'], + standalone: true, +}) +export class IonSplitPane { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionSplitPaneVisible']); + } +} + +export declare interface IonSplitPane extends Components.IonSplitPane { + /** + * Expression to be called when the split-pane visibility has changed + */ + ionSplitPaneVisible: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonTabBar, + inputs: ['color', 'mode', 'selectedTab', 'translucent'], +}) +@Component({ + selector: 'ion-tab-bar', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'mode', 'selectedTab', 'translucent'], + standalone: true, +}) +export class IonTabBar { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonTabBar extends Components.IonTabBar {} + +@ProxyCmp({ + defineCustomElementFn: defineIonTabButton, + inputs: ['disabled', 'download', 'href', 'layout', 'mode', 'rel', 'selected', 'tab', 'target'], +}) +@Component({ + selector: 'ion-tab-button', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['disabled', 'download', 'href', 'layout', 'mode', 'rel', 'selected', 'tab', 'target'], + standalone: true, +}) +export class IonTabButton { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonTabButton extends Components.IonTabButton {} + +@ProxyCmp({ + defineCustomElementFn: defineIonText, + inputs: ['color', 'mode'], +}) +@Component({ + selector: 'ion-text', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'mode'], + standalone: true, +}) +export class IonText { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonText extends Components.IonText {} + +@ProxyCmp({ + defineCustomElementFn: defineIonTextarea, + inputs: [ + 'autoGrow', + 'autocapitalize', + 'autofocus', + 'clearOnEdit', + 'color', + 'cols', + 'counter', + 'counterFormatter', + 'debounce', + 'disabled', + 'enterkeyhint', + 'errorText', + 'fill', + 'helperText', + 'inputmode', + 'label', + 'labelPlacement', + 'legacy', + 'maxlength', + 'minlength', + 'mode', + 'name', + 'placeholder', + 'readonly', + 'required', + 'rows', + 'shape', + 'spellcheck', + 'value', + 'wrap', + ], + methods: ['setFocus', 'getInputElement'], +}) +@Component({ + selector: 'ion-textarea', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [ + 'autoGrow', + 'autocapitalize', + 'autofocus', + 'clearOnEdit', + 'color', + 'cols', + 'counter', + 'counterFormatter', + 'debounce', + 'disabled', + 'enterkeyhint', + 'errorText', + 'fill', + 'helperText', + 'inputmode', + 'label', + 'labelPlacement', + 'legacy', + 'maxlength', + 'minlength', + 'mode', + 'name', + 'placeholder', + 'readonly', + 'required', + 'rows', + 'shape', + 'spellcheck', + 'value', + 'wrap', + ], + standalone: true, +}) +export class IonTextarea { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionChange', 'ionInput', 'ionBlur', 'ionFocus']); + } +} + +import type { TextareaChangeEventDetail as IIonTextareaTextareaChangeEventDetail } from '@ionic/core/components'; +import type { TextareaInputEventDetail as IIonTextareaTextareaInputEventDetail } from '@ionic/core/components'; + +export declare interface IonTextarea extends Components.IonTextarea { + /** + * The `ionChange` event is fired when the user modifies the textarea's value. +Unlike the `ionInput` event, the `ionChange` event is fired when +the element loses focus after its value has been modified. + */ + ionChange: EventEmitter>; + /** + * The `ionInput` event is fired each time the user modifies the textarea's value. +Unlike the `ionChange` event, the `ionInput` event is fired for each alteration +to the textarea's value. This typically happens for each keystroke as the user types. + +When `clearOnEdit` is enabled, the `ionInput` event will be fired when +the user clears the textarea by performing a keydown event. + */ + ionInput: EventEmitter>; + /** + * Emitted when the input loses focus. + */ + ionBlur: EventEmitter>; + /** + * Emitted when the input has focus. + */ + ionFocus: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonThumbnail, +}) +@Component({ + selector: 'ion-thumbnail', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [], + standalone: true, +}) +export class IonThumbnail { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonThumbnail extends Components.IonThumbnail {} + +@ProxyCmp({ + defineCustomElementFn: defineIonTitle, + inputs: ['color', 'size'], +}) +@Component({ + selector: 'ion-title', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'size'], + standalone: true, +}) +export class IonTitle { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonTitle extends Components.IonTitle {} + +@ProxyCmp({ + defineCustomElementFn: defineIonToast, + inputs: [ + 'animated', + 'buttons', + 'color', + 'cssClass', + 'duration', + 'enterAnimation', + 'header', + 'htmlAttributes', + 'icon', + 'isOpen', + 'keyboardClose', + 'layout', + 'leaveAnimation', + 'message', + 'mode', + 'position', + 'translucent', + 'trigger', + ], + methods: ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss'], +}) +@Component({ + selector: 'ion-toast', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [ + 'animated', + 'buttons', + 'color', + 'cssClass', + 'duration', + 'enterAnimation', + 'header', + 'htmlAttributes', + 'icon', + 'isOpen', + 'keyboardClose', + 'layout', + 'leaveAnimation', + 'message', + 'mode', + 'position', + 'translucent', + 'trigger', + ], + standalone: true, +}) +export class IonToast { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, [ + 'ionToastDidPresent', + 'ionToastWillPresent', + 'ionToastWillDismiss', + 'ionToastDidDismiss', + 'didPresent', + 'willPresent', + 'willDismiss', + 'didDismiss', + ]); + } +} + +import type { OverlayEventDetail as IIonToastOverlayEventDetail } from '@ionic/core/components'; + +export declare interface IonToast extends Components.IonToast { + /** + * Emitted after the toast has presented. + */ + ionToastDidPresent: EventEmitter>; + /** + * Emitted before the toast has presented. + */ + ionToastWillPresent: EventEmitter>; + /** + * Emitted before the toast has dismissed. + */ + ionToastWillDismiss: EventEmitter>; + /** + * Emitted after the toast has dismissed. + */ + ionToastDidDismiss: EventEmitter>; + /** + * Emitted after the toast has presented. +Shorthand for ionToastWillDismiss. + */ + didPresent: EventEmitter>; + /** + * Emitted before the toast has presented. +Shorthand for ionToastWillPresent. + */ + willPresent: EventEmitter>; + /** + * Emitted before the toast has dismissed. +Shorthand for ionToastWillDismiss. + */ + willDismiss: EventEmitter>; + /** + * Emitted after the toast has dismissed. +Shorthand for ionToastDidDismiss. + */ + didDismiss: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonToggle, + inputs: [ + 'checked', + 'color', + 'disabled', + 'enableOnOffLabels', + 'justify', + 'labelPlacement', + 'legacy', + 'mode', + 'name', + 'value', + ], +}) +@Component({ + selector: 'ion-toggle', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [ + 'checked', + 'color', + 'disabled', + 'enableOnOffLabels', + 'justify', + 'labelPlacement', + 'legacy', + 'mode', + 'name', + 'value', + ], + standalone: true, +}) +export class IonToggle { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionChange', 'ionFocus', 'ionBlur']); + } +} + +import type { ToggleChangeEventDetail as IIonToggleToggleChangeEventDetail } from '@ionic/core/components'; + +export declare interface IonToggle extends Components.IonToggle { + /** + * Emitted when the user switches the toggle on or off. Does not emit +when programmatically changing the value of the `checked` property. + */ + ionChange: EventEmitter>; + /** + * Emitted when the toggle has focus. + */ + ionFocus: EventEmitter>; + /** + * Emitted when the toggle loses focus. + */ + ionBlur: EventEmitter>; +} + +@ProxyCmp({ + defineCustomElementFn: defineIonToolbar, + inputs: ['color', 'mode'], +}) +@Component({ + selector: 'ion-toolbar', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'mode'], + standalone: true, +}) +export class IonToolbar { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + +export declare interface IonToolbar extends Components.IonToolbar {} diff --git a/packages/angular/standalone/src/index.ts b/packages/angular/standalone/src/index.ts index 4f012c4cd3b..b2e8af14fd4 100644 --- a/packages/angular/standalone/src/index.ts +++ b/packages/angular/standalone/src/index.ts @@ -3,7 +3,6 @@ export { IonModal } from './overlays/modal'; export { IonPopover } from './overlays/popover'; export { IonRouterOutlet } from './navigation/router-outlet'; export { IonRouterLink, IonRouterLinkWithHref } from './navigation/router-link-delegate'; -export { IonNav } from './navigation/nav-delegate'; export { provideIonicAngular } from './providers/ionic-angular'; export { ActionSheetController, @@ -23,3 +22,6 @@ export { NavParams, IonicRouteStrategy, } from '@ionic/angular/common'; +export { IonNav } from './navigation/nav'; +export { IonIcon } from './directives/icon'; +export * from './directives/proxies'; diff --git a/packages/angular/standalone/src/navigation/back-button.ts b/packages/angular/standalone/src/navigation/back-button.ts index d9e98711623..84462ebb01a 100644 --- a/packages/angular/standalone/src/navigation/back-button.ts +++ b/packages/angular/standalone/src/navigation/back-button.ts @@ -1,10 +1,5 @@ -import { Component, Optional, ChangeDetectionStrategy, ElementRef, NgZone } from '@angular/core'; -import { - IonBackButtonDelegate as IonBackButtonDelegateBase, - NavController, - Config, - ProxyCmp, -} from '@ionic/angular/common'; +import { Component, Optional, ChangeDetectionStrategy, ElementRef, NgZone, ChangeDetectorRef } from '@angular/core'; +import { IonBackButton as IonBackButtonBase, NavController, Config, ProxyCmp } from '@ionic/angular/common'; import { defineCustomElement } from '@ionic/core/components/ion-back-button.js'; import { IonRouterOutlet } from './router-outlet'; @@ -19,14 +14,15 @@ import { IonRouterOutlet } from './router-outlet'; standalone: true, }) // eslint-disable-next-line @angular-eslint/directive-class-suffix -export class IonBackButton extends IonBackButtonDelegateBase { +export class IonBackButton extends IonBackButtonBase { constructor( @Optional() routerOutlet: IonRouterOutlet, navCtrl: NavController, config: Config, r: ElementRef, - z: NgZone + z: NgZone, + c: ChangeDetectorRef ) { - super(routerOutlet, navCtrl, config, r, z); + super(routerOutlet, navCtrl, config, r, z, c); } } diff --git a/packages/angular/standalone/src/navigation/nav-delegate.ts b/packages/angular/standalone/src/navigation/nav.ts similarity index 69% rename from packages/angular/standalone/src/navigation/nav-delegate.ts rename to packages/angular/standalone/src/navigation/nav.ts index 410f45e635c..a8c3bb1b009 100644 --- a/packages/angular/standalone/src/navigation/nav-delegate.ts +++ b/packages/angular/standalone/src/navigation/nav.ts @@ -1,5 +1,5 @@ -import { Component, ElementRef, Injector, EnvironmentInjector, NgZone } from '@angular/core'; -import { NavDelegate as NavDelegateBase, ProxyCmp, AngularDelegate } from '@ionic/angular/common'; +import { Component, ElementRef, Injector, EnvironmentInjector, NgZone, ChangeDetectorRef } from '@angular/core'; +import { IonNav as IonNavBase, ProxyCmp, AngularDelegate } from '@ionic/angular/common'; import { defineCustomElement } from '@ionic/core/components/ion-nav.js'; @ProxyCmp({ @@ -10,14 +10,15 @@ import { defineCustomElement } from '@ionic/core/components/ion-nav.js'; template: '', standalone: true, }) -export class IonNav extends NavDelegateBase { +export class IonNav extends IonNavBase { constructor( ref: ElementRef, environmentInjector: EnvironmentInjector, injector: Injector, angularDelegate: AngularDelegate, - z: NgZone + z: NgZone, + c: ChangeDetectorRef ) { - super(ref, environmentInjector, injector, angularDelegate, z); + super(ref, environmentInjector, injector, angularDelegate, z, c); } } diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.component.html b/packages/angular/test/base/src/app/standalone/app-standalone/app.component.html index 15a92a22447..13b96776ee1 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.component.html +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.component.html @@ -1 +1,3 @@ - + + + diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.component.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.component.ts index e1058486487..d51d38c860b 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.component.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.component.ts @@ -1,12 +1,12 @@ import { Component } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { IonRouterOutlet } from '@ionic/angular/standalone'; +import { IonRouterOutlet, IonApp } from '@ionic/angular/standalone'; @Component({ selector: 'app-root-standalone', templateUrl: './app.component.html', standalone: true, - imports: [RouterModule, IonRouterOutlet] + imports: [RouterModule, IonRouterOutlet, IonApp] }) export class AppComponent { } diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts index fb027e34f98..1026d18b627 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts @@ -6,14 +6,15 @@ export const routes: Routes = [ path: '', component: AppComponent, children: [ - { path: 'popover', loadComponent: () => import('../popover/popover.component').then(m => m.PopoverComponent) }, - { path: 'modal', loadComponent: () => import('../modal/modal.component').then(m => m.ModalComponent) }, - { path: 'router-outlet', loadComponent: () => import('../router-outlet/router-outlet.component').then(m => m.RouterOutletComponent) }, - { path: 'back-button', loadComponent: () => import('../back-button/back-button.component').then(m => m.BackButtonComponent) }, - { path: 'router-link', loadComponent: () => import('../router-link/router-link.component').then(m => m.RouterLinkComponent) }, - { path: 'nav', loadComponent: () => import('../nav/nav.component').then(m => m.NavComponent) }, - { path: 'providers', loadComponent: () => import('../providers/providers.component').then(m => m.ProvidersComponent) }, - { path: 'overlay-controllers', loadComponent: () => import('../overlay-controllers/overlay-controllers.component').then(m => m.OverlayControllersComponent) }, + { path: 'popover', loadComponent: () => import('../popover/popover.component').then(c => c.PopoverComponent) }, + { path: 'modal', loadComponent: () => import('../modal/modal.component').then(c => c.ModalComponent) }, + { path: 'router-outlet', loadComponent: () => import('../router-outlet/router-outlet.component').then(c => c.RouterOutletComponent) }, + { path: 'back-button', loadComponent: () => import('../back-button/back-button.component').then(c => c.BackButtonComponent) }, + { path: 'router-link', loadComponent: () => import('../router-link/router-link.component').then(c => c.RouterLinkComponent) }, + { path: 'nav', loadComponent: () => import('../nav/nav.component').then(c => c.NavComponent) }, + { path: 'providers', loadComponent: () => import('../providers/providers.component').then(c => c.ProvidersComponent) }, + { path: 'overlay-controllers', loadComponent: () => import('../overlay-controllers/overlay-controllers.component').then(c => c.OverlayControllersComponent) }, + { path: 'button', loadComponent: () => import('../button/button.component').then(c => c.ButtonComponent) } ] }, ]; diff --git a/packages/angular/test/base/src/app/standalone/button/button.component.ts b/packages/angular/test/base/src/app/standalone/button/button.component.ts new file mode 100644 index 00000000000..ec3869ebe76 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/button/button.component.ts @@ -0,0 +1,12 @@ +import { Component } from "@angular/core"; +import { IonButton } from '@ionic/angular/standalone'; + +@Component({ + selector: 'app-button', + template: `Test`, + standalone: true, + imports: [IonButton] +}) +export class ButtonComponent { + +} From e5b7f5ff55874d22559f426796b838e6e22d7dd0 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Tue, 22 Aug 2023 08:46:46 -0500 Subject: [PATCH 17/24] feat(angular): add enhanced icon support with addIcons (#28009) --- .../angular/test/apps/ng14/package-lock.json | 35 ++++++++-- packages/angular/test/apps/ng14/package.json | 2 +- .../angular/test/apps/ng15/package-lock.json | 35 ++++++++-- packages/angular/test/apps/ng15/package.json | 2 +- .../angular/test/apps/ng16/package-lock.json | 67 +++++++++++++++++-- packages/angular/test/apps/ng16/package.json | 2 +- .../test/base/e2e/src/standalone/icon.spec.ts | 20 ++++++ .../standalone/app-standalone/app.routes.ts | 3 +- .../app/standalone/icon/icon.component.html | 6 ++ .../src/app/standalone/icon/icon.component.ts | 27 ++++++++ 10 files changed, 174 insertions(+), 25 deletions(-) create mode 100644 packages/angular/test/base/e2e/src/standalone/icon.spec.ts create mode 100644 packages/angular/test/base/src/app/standalone/icon/icon.component.html create mode 100644 packages/angular/test/base/src/app/standalone/icon/icon.component.ts diff --git a/packages/angular/test/apps/ng14/package-lock.json b/packages/angular/test/apps/ng14/package-lock.json index 8f559970248..5e258386991 100644 --- a/packages/angular/test/apps/ng14/package-lock.json +++ b/packages/angular/test/apps/ng14/package-lock.json @@ -22,7 +22,7 @@ "@ionic/angular-server": "^6.1.15", "core-js": "^2.6.11", "express": "^4.15.2", - "ionicons": "^6.0.4", + "ionicons": "^7.1.3-dev.11692630068.1f5f09ee", "rxjs": "~7.5.0", "tslib": "^2.3.0", "typescript-eslint-language-service": "^4.1.5", @@ -52,6 +52,9 @@ "wait-on": "^5.2.1", "webpack": "^5.61.0", "webpack-cli": "^4.9.2" + }, + "engines": { + "node": ">= 16" } }, "node_modules/@ampproject/remapping": { @@ -2928,6 +2931,14 @@ "tslib": "^2.1.0" } }, + "node_modules/@ionic/core/node_modules/ionicons": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-6.1.3.tgz", + "integrity": "sha512-ptzz38dd/Yq+PgjhXegh7yhb/SLIk1bvL9vQDtLv1aoSc7alO6mX2DIMgcKYzt9vrNWkRu1f9Jr78zIFFyOXqw==", + "dependencies": { + "@stencil/core": "^2.18.0" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -9968,9 +9979,9 @@ } }, "node_modules/ionicons": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-6.0.4.tgz", - "integrity": "sha512-uDNOkBo0OVYV+kIhb51g9mb7r3Z0b+78GPZQBsjXuaetNmrB/mNTqN/uFtO+vxL/rQySKjzk8qeKJI5NWL9Ueg==", + "version": "7.1.3-dev.11692630068.1f5f09ee", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.3-dev.11692630068.1f5f09ee.tgz", + "integrity": "sha512-UadZRbi50/IFJM5sIa3X7+c1sVsA0AB7uLrLyH7SRTX2MCol7clT1gfw/si28tG0NWScDyvbdC2SgzhN7lkroA==", "dependencies": { "@stencil/core": "^2.18.0" } @@ -18570,6 +18581,16 @@ "@stencil/core": "^2.16.0", "ionicons": "^6.0.2", "tslib": "^2.1.0" + }, + "dependencies": { + "ionicons": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-6.1.3.tgz", + "integrity": "sha512-ptzz38dd/Yq+PgjhXegh7yhb/SLIk1bvL9vQDtLv1aoSc7alO6mX2DIMgcKYzt9vrNWkRu1f9Jr78zIFFyOXqw==", + "requires": { + "@stencil/core": "^2.18.0" + } + } } }, "@istanbuljs/load-nyc-config": { @@ -23769,9 +23790,9 @@ "dev": true }, "ionicons": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-6.0.4.tgz", - "integrity": "sha512-uDNOkBo0OVYV+kIhb51g9mb7r3Z0b+78GPZQBsjXuaetNmrB/mNTqN/uFtO+vxL/rQySKjzk8qeKJI5NWL9Ueg==", + "version": "7.1.3-dev.11692630068.1f5f09ee", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.3-dev.11692630068.1f5f09ee.tgz", + "integrity": "sha512-UadZRbi50/IFJM5sIa3X7+c1sVsA0AB7uLrLyH7SRTX2MCol7clT1gfw/si28tG0NWScDyvbdC2SgzhN7lkroA==", "requires": { "@stencil/core": "^2.18.0" } diff --git a/packages/angular/test/apps/ng14/package.json b/packages/angular/test/apps/ng14/package.json index ec700a1a888..7515f7b7283 100644 --- a/packages/angular/test/apps/ng14/package.json +++ b/packages/angular/test/apps/ng14/package.json @@ -33,7 +33,7 @@ "@ionic/angular-server": "^6.1.15", "core-js": "^2.6.11", "express": "^4.15.2", - "ionicons": "^6.0.4", + "ionicons": "7.1.3-dev.11692630068.1f5f09ee", "rxjs": "~7.5.0", "tslib": "^2.3.0", "typescript-eslint-language-service": "^4.1.5", diff --git a/packages/angular/test/apps/ng15/package-lock.json b/packages/angular/test/apps/ng15/package-lock.json index 3049ab8a793..a47d40dc489 100644 --- a/packages/angular/test/apps/ng15/package-lock.json +++ b/packages/angular/test/apps/ng15/package-lock.json @@ -23,7 +23,7 @@ "@nguniversal/express-engine": "^15.0.0", "core-js": "^2.6.11", "express": "^4.15.2", - "ionicons": "^6.0.4", + "ionicons": "7.1.3-dev.11692630068.1f5f09ee", "rxjs": "~7.5.0", "tslib": "^2.3.0", "typescript-eslint-language-service": "^4.1.5", @@ -53,6 +53,9 @@ "wait-on": "^5.2.1", "webpack": "^5.61.0", "webpack-cli": "^4.9.2" + }, + "engines": { + "node": ">= 16" } }, "node_modules/@ampproject/remapping": { @@ -2653,6 +2656,14 @@ "tslib": "^2.1.0" } }, + "node_modules/@ionic/core/node_modules/ionicons": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-6.1.3.tgz", + "integrity": "sha512-ptzz38dd/Yq+PgjhXegh7yhb/SLIk1bvL9vQDtLv1aoSc7alO6mX2DIMgcKYzt9vrNWkRu1f9Jr78zIFFyOXqw==", + "dependencies": { + "@stencil/core": "^2.18.0" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -9560,9 +9571,9 @@ } }, "node_modules/ionicons": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-6.0.4.tgz", - "integrity": "sha512-uDNOkBo0OVYV+kIhb51g9mb7r3Z0b+78GPZQBsjXuaetNmrB/mNTqN/uFtO+vxL/rQySKjzk8qeKJI5NWL9Ueg==", + "version": "7.1.3-dev.11692630068.1f5f09ee", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.3-dev.11692630068.1f5f09ee.tgz", + "integrity": "sha512-UadZRbi50/IFJM5sIa3X7+c1sVsA0AB7uLrLyH7SRTX2MCol7clT1gfw/si28tG0NWScDyvbdC2SgzhN7lkroA==", "dependencies": { "@stencil/core": "^2.18.0" } @@ -17575,6 +17586,16 @@ "@stencil/core": "^2.16.0", "ionicons": "^6.0.2", "tslib": "^2.1.0" + }, + "dependencies": { + "ionicons": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-6.1.3.tgz", + "integrity": "sha512-ptzz38dd/Yq+PgjhXegh7yhb/SLIk1bvL9vQDtLv1aoSc7alO6mX2DIMgcKYzt9vrNWkRu1f9Jr78zIFFyOXqw==", + "requires": { + "@stencil/core": "^2.18.0" + } + } } }, "@istanbuljs/load-nyc-config": { @@ -22669,9 +22690,9 @@ "dev": true }, "ionicons": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-6.0.4.tgz", - "integrity": "sha512-uDNOkBo0OVYV+kIhb51g9mb7r3Z0b+78GPZQBsjXuaetNmrB/mNTqN/uFtO+vxL/rQySKjzk8qeKJI5NWL9Ueg==", + "version": "7.1.3-dev.11692630068.1f5f09ee", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.3-dev.11692630068.1f5f09ee.tgz", + "integrity": "sha512-UadZRbi50/IFJM5sIa3X7+c1sVsA0AB7uLrLyH7SRTX2MCol7clT1gfw/si28tG0NWScDyvbdC2SgzhN7lkroA==", "requires": { "@stencil/core": "^2.18.0" } diff --git a/packages/angular/test/apps/ng15/package.json b/packages/angular/test/apps/ng15/package.json index 0829dd3e086..430252407e8 100644 --- a/packages/angular/test/apps/ng15/package.json +++ b/packages/angular/test/apps/ng15/package.json @@ -34,7 +34,7 @@ "@nguniversal/express-engine": "^15.0.0", "core-js": "^2.6.11", "express": "^4.15.2", - "ionicons": "^6.0.4", + "ionicons": "7.1.3-dev.11692630068.1f5f09ee", "rxjs": "~7.5.0", "tslib": "^2.3.0", "typescript-eslint-language-service": "^4.1.5", diff --git a/packages/angular/test/apps/ng16/package-lock.json b/packages/angular/test/apps/ng16/package-lock.json index 762baf3cd90..99e0de1acd6 100644 --- a/packages/angular/test/apps/ng16/package-lock.json +++ b/packages/angular/test/apps/ng16/package-lock.json @@ -22,7 +22,7 @@ "@nguniversal/express-engine": "^16.0.0", "core-js": "^2.6.11", "express": "^4.15.2", - "ionicons": "^7.0.4", + "ionicons": "7.1.3-dev.11692630068.1f5f09ee", "rxjs": "~7.8.0", "tslib": "^2.3.0", "typescript-eslint-language-service": "^4.1.5", @@ -3230,6 +3230,14 @@ "zone.js": ">=0.11.0" } }, + "node_modules/@ionic/angular/node_modules/ionicons": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.2.tgz", + "integrity": "sha512-zZ4njAqSP39H8RRvZhJvkHsv7cBjYE/VfInH218Osf2UVxJITSOutTTd25MW+tAXKN5fheYzclUXUsF55JHUDg==", + "dependencies": { + "@stencil/core": "^2.18.0" + } + }, "node_modules/@ionic/core": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.0.2.tgz", @@ -3252,6 +3260,26 @@ "npm": ">=6.0.0" } }, + "node_modules/@ionic/core/node_modules/ionicons": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.2.tgz", + "integrity": "sha512-zZ4njAqSP39H8RRvZhJvkHsv7cBjYE/VfInH218Osf2UVxJITSOutTTd25MW+tAXKN5fheYzclUXUsF55JHUDg==", + "dependencies": { + "@stencil/core": "^2.18.0" + } + }, + "node_modules/@ionic/core/node_modules/ionicons/node_modules/@stencil/core": { + "version": "2.22.3", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.22.3.tgz", + "integrity": "sha512-kmVA0M/HojwsfkeHsifvHVIYe4l5tin7J5+DLgtl8h6WWfiMClND5K3ifCXXI2ETDNKiEk21p6jql3Fx9o2rng==", + "bin": { + "stencil": "bin/stencil" + }, + "engines": { + "node": ">=12.10.0", + "npm": ">=6.0.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -10142,9 +10170,9 @@ } }, "node_modules/ionicons": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.0.tgz", - "integrity": "sha512-iE4GuEdEHARJpp0sWL7WJZCzNCf5VxpNRhAjW0fLnZPnNL5qZOJUcfup2Z2Ty7Jk8Q5hacrHfGEB1lCwOdXqGg==", + "version": "7.1.3-dev.11692630068.1f5f09ee", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.3-dev.11692630068.1f5f09ee.tgz", + "integrity": "sha512-UadZRbi50/IFJM5sIa3X7+c1sVsA0AB7uLrLyH7SRTX2MCol7clT1gfw/si28tG0NWScDyvbdC2SgzhN7lkroA==", "dependencies": { "@stencil/core": "^2.18.0" } @@ -19155,6 +19183,16 @@ "ionicons": "^7.0.0", "jsonc-parser": "^3.0.0", "tslib": "^2.3.0" + }, + "dependencies": { + "ionicons": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.2.tgz", + "integrity": "sha512-zZ4njAqSP39H8RRvZhJvkHsv7cBjYE/VfInH218Osf2UVxJITSOutTTd25MW+tAXKN5fheYzclUXUsF55JHUDg==", + "requires": { + "@stencil/core": "^2.18.0" + } + } } }, "@ionic/angular-server": { @@ -19180,6 +19218,21 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.2.1.tgz", "integrity": "sha512-Ybm4NteQBScLq3H0JML/uqo4nWjNpZw1HAAURtR5LlRm7ptzNKO5S8EnHp3m05/uyTzeh9yLpUFHY7bxGNdYLg==" + }, + "ionicons": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.2.tgz", + "integrity": "sha512-zZ4njAqSP39H8RRvZhJvkHsv7cBjYE/VfInH218Osf2UVxJITSOutTTd25MW+tAXKN5fheYzclUXUsF55JHUDg==", + "requires": { + "@stencil/core": "^2.18.0" + }, + "dependencies": { + "@stencil/core": { + "version": "2.22.3", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.22.3.tgz", + "integrity": "sha512-kmVA0M/HojwsfkeHsifvHVIYe4l5tin7J5+DLgtl8h6WWfiMClND5K3ifCXXI2ETDNKiEk21p6jql3Fx9o2rng==" + } + } } } }, @@ -24312,9 +24365,9 @@ "dev": true }, "ionicons": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.0.tgz", - "integrity": "sha512-iE4GuEdEHARJpp0sWL7WJZCzNCf5VxpNRhAjW0fLnZPnNL5qZOJUcfup2Z2Ty7Jk8Q5hacrHfGEB1lCwOdXqGg==", + "version": "7.1.3-dev.11692630068.1f5f09ee", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.3-dev.11692630068.1f5f09ee.tgz", + "integrity": "sha512-UadZRbi50/IFJM5sIa3X7+c1sVsA0AB7uLrLyH7SRTX2MCol7clT1gfw/si28tG0NWScDyvbdC2SgzhN7lkroA==", "requires": { "@stencil/core": "^2.18.0" } diff --git a/packages/angular/test/apps/ng16/package.json b/packages/angular/test/apps/ng16/package.json index dc108ed7c4d..b462d5e1390 100644 --- a/packages/angular/test/apps/ng16/package.json +++ b/packages/angular/test/apps/ng16/package.json @@ -33,7 +33,7 @@ "@nguniversal/express-engine": "^16.0.0", "core-js": "^2.6.11", "express": "^4.15.2", - "ionicons": "^7.0.4", + "ionicons": "7.1.3-dev.11692630068.1f5f09ee", "rxjs": "~7.8.0", "tslib": "^2.3.0", "typescript-eslint-language-service": "^4.1.5", diff --git a/packages/angular/test/base/e2e/src/standalone/icon.spec.ts b/packages/angular/test/base/e2e/src/standalone/icon.spec.ts new file mode 100644 index 00000000000..0f1e9f7f7b3 --- /dev/null +++ b/packages/angular/test/base/e2e/src/standalone/icon.spec.ts @@ -0,0 +1,20 @@ +describe('Icons', () => { + it('should render an icon', () => { + cy.visit('/standalone/icon'); + + cy.get('ion-icon#icon-string').shadow().find('svg').should('exist'); + cy.get('ion-icon#icon-binding').shadow().find('svg').should('exist'); + }); + + it('should render an icon on iOS mode', () => { + cy.visit('/standalone/icon?ionic:mode=ios'); + + cy.get('ion-icon#icon-mode').shadow().find('svg').should('exist'); + }); + + it('should render an icon on MD mode', () => { + cy.visit('/standalone/icon?ionic:mode=md'); + + cy.get('ion-icon#icon-mode').shadow().find('svg').should('exist'); + }); +}); diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts index 1026d18b627..2367c617729 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts @@ -14,7 +14,8 @@ export const routes: Routes = [ { path: 'nav', loadComponent: () => import('../nav/nav.component').then(c => c.NavComponent) }, { path: 'providers', loadComponent: () => import('../providers/providers.component').then(c => c.ProvidersComponent) }, { path: 'overlay-controllers', loadComponent: () => import('../overlay-controllers/overlay-controllers.component').then(c => c.OverlayControllersComponent) }, - { path: 'button', loadComponent: () => import('../button/button.component').then(c => c.ButtonComponent) } + { path: 'button', loadComponent: () => import('../button/button.component').then(c => c.ButtonComponent) }, + { path: 'icon', loadComponent: () => import('../icon/icon.component').then(c => c.IconComponent) }, ] }, ]; diff --git a/packages/angular/test/base/src/app/standalone/icon/icon.component.html b/packages/angular/test/base/src/app/standalone/icon/icon.component.html new file mode 100644 index 00000000000..f87898d684e --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/icon/icon.component.html @@ -0,0 +1,6 @@ +
+ Icon Name:
+ Icon Name Binding:
+ iOS/MD:
+ iOS/MD Binding:
+
diff --git a/packages/angular/test/base/src/app/standalone/icon/icon.component.ts b/packages/angular/test/base/src/app/standalone/icon/icon.component.ts new file mode 100644 index 00000000000..2f027a2eb8e --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/icon/icon.component.ts @@ -0,0 +1,27 @@ +import { Component } from '@angular/core'; +import { IonIcon } from '@ionic/angular/standalone'; +import { logoIonic, logoIonitron, logoApple, logoAndroid } from 'ionicons/icons'; +import { addIcons } from 'ionicons'; + +addIcons({ logoIonic, logoIonitron, logoApple, logoAndroid }); + +@Component({ + selector: 'app-icon', + templateUrl: './icon.component.html', + standalone: true, + imports: [IonIcon], +}) +export class IconComponent { + iconName: string = 'logo-ionic'; + iosIcon: string = 'logo-apple'; + mdIcon: string = 'logo-android'; + + changeIcon() { + this.iconName = this.iconName === 'logo-ionic' ? 'logo-apple' : 'logo-ionic'; + } + + changeModeIcon() { + this.iosIcon = this.iosIcon === 'logo-apple' ? 'logo-ionic' : 'logo-apple'; + this.mdIcon = this.mdIcon === 'logo-android' ? 'logo-ionitron' : 'logo-android'; + } +} From 0afa14ed7a7af4a85293eed2199dff5f36653f45 Mon Sep 17 00:00:00 2001 From: Amanda Johnston <90629384+amandaejohnston@users.noreply.github.com> Date: Fri, 1 Sep 2023 13:08:20 -0500 Subject: [PATCH 18/24] feat(angular): add standalone tabs (#28093) Issue number: N/A --------- ## What is the current behavior? Tabs cannot be used as a standalone component. ## What is the new behavior? - Added tabs as a standalone component. - Added a quick test. I included the event checking from the lazy test in the HTML as a smoke check, but didn't bring over the Cypress tests for them since I noticed the other standalone tests have been quick, stripped down affairs. I'm assuming I would just be duplicating effort. Let me know if I should bring more tests over from the lazy version, or even get rid of the event logging from the standalone HTML. ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information --- packages/angular/standalone/src/index.ts | 1 + .../angular/standalone/src/navigation/tabs.ts | 52 +++++++++++++++++++ .../test/base/e2e/src/standalone/tabs.spec.ts | 16 ++++++ .../standalone/app-standalone/app.routes.ts | 10 ++++ .../src/app/standalone/tabs/tab1.component.ts | 12 +++++ .../src/app/standalone/tabs/tab2.component.ts | 12 +++++ .../src/app/standalone/tabs/tab3.component.ts | 12 +++++ .../app/standalone/tabs/tabs.component.html | 42 +++++++++++++++ .../src/app/standalone/tabs/tabs.component.ts | 38 ++++++++++++++ 9 files changed, 195 insertions(+) create mode 100644 packages/angular/standalone/src/navigation/tabs.ts create mode 100644 packages/angular/test/base/e2e/src/standalone/tabs.spec.ts create mode 100644 packages/angular/test/base/src/app/standalone/tabs/tab1.component.ts create mode 100644 packages/angular/test/base/src/app/standalone/tabs/tab2.component.ts create mode 100644 packages/angular/test/base/src/app/standalone/tabs/tab3.component.ts create mode 100644 packages/angular/test/base/src/app/standalone/tabs/tabs.component.html create mode 100644 packages/angular/test/base/src/app/standalone/tabs/tabs.component.ts diff --git a/packages/angular/standalone/src/index.ts b/packages/angular/standalone/src/index.ts index b2e8af14fd4..8bd5de65c8b 100644 --- a/packages/angular/standalone/src/index.ts +++ b/packages/angular/standalone/src/index.ts @@ -3,6 +3,7 @@ export { IonModal } from './overlays/modal'; export { IonPopover } from './overlays/popover'; export { IonRouterOutlet } from './navigation/router-outlet'; export { IonRouterLink, IonRouterLinkWithHref } from './navigation/router-link-delegate'; +export { IonTabs } from './navigation/tabs'; export { provideIonicAngular } from './providers/ionic-angular'; export { ActionSheetController, diff --git a/packages/angular/standalone/src/navigation/tabs.ts b/packages/angular/standalone/src/navigation/tabs.ts new file mode 100644 index 00000000000..b54be17934f --- /dev/null +++ b/packages/angular/standalone/src/navigation/tabs.ts @@ -0,0 +1,52 @@ +import { Component, ContentChild, ContentChildren, ViewChild, QueryList } from '@angular/core'; +import { IonTabs as IonTabsBase } from '@ionic/angular/common'; + +import { IonTabBar } from '../directives/proxies'; + +import { IonRouterOutlet } from './router-outlet'; + +@Component({ + selector: 'ion-tabs', + template: ` + +
+ +
+ + `, + standalone: true, + styles: [ + ` + :host { + display: flex; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + + flex-direction: column; + + width: 100%; + height: 100%; + + contain: layout size style; + } + .tabs-inner { + position: relative; + + flex: 1; + + contain: layout size style; + } + `, + ], + imports: [IonRouterOutlet], +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +export class IonTabs extends IonTabsBase { + @ViewChild('outlet', { read: IonRouterOutlet, static: false }) outlet: IonRouterOutlet; + + @ContentChild(IonTabBar, { static: false }) tabBar: IonTabBar | undefined; + @ContentChildren(IonTabBar) tabBars: QueryList; +} diff --git a/packages/angular/test/base/e2e/src/standalone/tabs.spec.ts b/packages/angular/test/base/e2e/src/standalone/tabs.spec.ts new file mode 100644 index 00000000000..a0558f299fa --- /dev/null +++ b/packages/angular/test/base/e2e/src/standalone/tabs.spec.ts @@ -0,0 +1,16 @@ +describe('Tabs', () => { + beforeEach(() => { + cy.visit('/standalone/tabs'); + }); + + it('should redirect to the default tab', () => { + cy.get('app-tab-one').should('be.visible'); + cy.contains('Tab 1'); + }); + + it('should render new content when switching tabs', () => { + cy.get('#tab-button-tab-two').click(); + cy.get('app-tab-two').should('be.visible'); + cy.contains('Tab 2'); + }); +}); diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts index 2367c617729..d97d34c66bb 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts @@ -16,6 +16,16 @@ export const routes: Routes = [ { path: 'overlay-controllers', loadComponent: () => import('../overlay-controllers/overlay-controllers.component').then(c => c.OverlayControllersComponent) }, { path: 'button', loadComponent: () => import('../button/button.component').then(c => c.ButtonComponent) }, { path: 'icon', loadComponent: () => import('../icon/icon.component').then(c => c.IconComponent) }, + { path: 'tabs', redirectTo: '/standalone/tabs/tab-one', pathMatch: 'full' }, + { + path: 'tabs', + loadComponent: () => import('../tabs/tabs.component').then(c => c.TabsComponent), + children: [ + { path: 'tab-one', loadComponent: () => import('../tabs/tab1.component').then(c => c.TabOneComponent) }, + { path: 'tab-two', loadComponent: () => import('../tabs/tab2.component').then(c => c.TabTwoComponent) }, + { path: 'tab-three', loadComponent: () => import('../tabs/tab3.component').then(c => c.TabThreeComponent) } + ] + }, ] }, ]; diff --git a/packages/angular/test/base/src/app/standalone/tabs/tab1.component.ts b/packages/angular/test/base/src/app/standalone/tabs/tab1.component.ts new file mode 100644 index 00000000000..8b6e8976ec5 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/tabs/tab1.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-tab-one', + template: ` + Tab 1 + `, + standalone: true, +}) +export class TabOneComponent { + +} diff --git a/packages/angular/test/base/src/app/standalone/tabs/tab2.component.ts b/packages/angular/test/base/src/app/standalone/tabs/tab2.component.ts new file mode 100644 index 00000000000..b27b301fdd5 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/tabs/tab2.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-tab-two', + template: ` + Tab 2 + `, + standalone: true, +}) +export class TabTwoComponent { + +} diff --git a/packages/angular/test/base/src/app/standalone/tabs/tab3.component.ts b/packages/angular/test/base/src/app/standalone/tabs/tab3.component.ts new file mode 100644 index 00000000000..b2af4e6550e --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/tabs/tab3.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-tab-three', + template: ` + Tab 3 + `, + standalone: true, +}) +export class TabThreeComponent { + +} diff --git a/packages/angular/test/base/src/app/standalone/tabs/tabs.component.html b/packages/angular/test/base/src/app/standalone/tabs/tabs.component.html new file mode 100644 index 00000000000..02105307cea --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/tabs/tabs.component.html @@ -0,0 +1,42 @@ + + + + Tab One + + + + + Tab Two + + + + + Tab Three + + + + +
+
    +
  • + ionTabsWillChange counter: {{ tabsWillChangeCounter }} +
  • +
  • + ionTabsWillChange event: {{ tabsWillChangeEvent }} +
  • +
  • + ionTabsWillChange selectedTab: {{ tabsWillChangeSelectedTab }} +
  • +
+
    +
  • + ionTabsDidChange counter: {{ tabsDidChangeCounter }} +
  • +
  • + ionTabsDidChange event: {{ tabsDidChangeEvent }} +
  • +
  • + ionTabsDidChange selectedTab: {{ tabsDidChangeSelectedTab }} +
  • +
+
\ No newline at end of file diff --git a/packages/angular/test/base/src/app/standalone/tabs/tabs.component.ts b/packages/angular/test/base/src/app/standalone/tabs/tabs.component.ts new file mode 100644 index 00000000000..d4b889e0dd1 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/tabs/tabs.component.ts @@ -0,0 +1,38 @@ +import { Component, ViewChild } from '@angular/core'; +import { IonTabBar, IonTabButton, IonIcon, IonLabel, IonTabs } from '@ionic/angular/standalone'; +import { addIcons } from 'ionicons'; +import { add, logoIonic, save } from 'ionicons/icons'; + +addIcons({ add, logoIonic, save }); + +@Component({ + selector: 'app-tabs', + templateUrl: './tabs.component.html', + standalone: true, + imports: [IonTabBar, IonTabButton, IonIcon, IonLabel, IonTabs] +}) +export class TabsComponent { + tabsDidChangeCounter = 0; + tabsDidChangeEvent = ''; + tabsDidChangeSelectedTab? = ''; + + tabsWillChangeCounter = 0; + tabsWillChangeEvent = ''; + tabsWillChangeSelectedTab? = ''; + + @ViewChild(IonTabBar) tabBar!: IonTabBar; + + tabChanged(ev: { tab: string }) { + console.log('ionTabsDidChange', this.tabBar.selectedTab); + this.tabsDidChangeCounter++; + this.tabsDidChangeEvent = ev.tab; + this.tabsDidChangeSelectedTab = this.tabBar.selectedTab; + } + + tabsWillChange(ev: { tab: string }) { + console.log('ionTabsWillChange', this.tabBar.selectedTab); + this.tabsWillChangeCounter++; + this.tabsWillChangeEvent = ev.tab; + this.tabsWillChangeSelectedTab = this.tabBar.selectedTab; + } +} From 01c34738a651613dd8da279b6d27a71a9eb3e821 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Wed, 6 Sep 2023 14:07:56 -0700 Subject: [PATCH 19/24] chore(angular): add missing code from bbfb8f8 --- .../directives/navigation/router-outlet.ts | 17 +++++++++++--- .../common/src/directives/navigation/tabs.ts | 23 +++++++++++++++---- packages/angular/common/src/index.ts | 2 +- .../angular/standalone/src/navigation/tabs.ts | 7 +++++- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/packages/angular/common/src/directives/navigation/router-outlet.ts b/packages/angular/common/src/directives/navigation/router-outlet.ts index 6534bd55870..d0d6bb600c2 100644 --- a/packages/angular/common/src/directives/navigation/router-outlet.ts +++ b/packages/angular/common/src/directives/navigation/router-outlet.ts @@ -31,7 +31,7 @@ import { Config } from '../../providers/config'; import { NavController } from '../../providers/nav-controller'; import { StackController } from './stack-controller'; -import { RouteView, getUrl } from './stack-utils'; +import { RouteView, StackDidChangeEvent, StackWillChangeEvent, getUrl, isTabSwitch } from './stack-utils'; // TODO(FW-2827): types @@ -67,7 +67,11 @@ export class IonRouterOutlet implements OnDestroy, OnInit { */ @Input() name = PRIMARY_OUTLET; - @Output() stackEvents = new EventEmitter(); + /** @internal */ + @Output() stackWillChange = new EventEmitter(); + /** @internal */ + @Output() stackDidChange = new EventEmitter(); + // eslint-disable-next-line @angular-eslint/no-output-rename @Output('activate') activateEvents = new EventEmitter(); // eslint-disable-next-line @angular-eslint/no-output-rename @@ -305,9 +309,16 @@ export class IonRouterOutlet implements OnDestroy, OnInit { */ this.navCtrl.setTopOutlet(this); + const leavingView = this.stackCtrl.getActiveView(); + + this.stackWillChange.emit({ + enteringView, + tabSwitch: isTabSwitch(enteringView, leavingView), + }); + this.stackCtrl.setActive(enteringView).then((data) => { this.activateEvents.emit(cmpRef.instance); - this.stackEvents.emit(data); + this.stackDidChange.emit(data); }); } diff --git a/packages/angular/common/src/directives/navigation/tabs.ts b/packages/angular/common/src/directives/navigation/tabs.ts index 9807ea8bed7..1e902fc2d7f 100644 --- a/packages/angular/common/src/directives/navigation/tabs.ts +++ b/packages/angular/common/src/directives/navigation/tabs.ts @@ -11,7 +11,7 @@ import { import { NavController } from '../../providers/nav-controller'; -import type { StackEvent } from './stack-utils'; +import { StackDidChangeEvent, StackWillChangeEvent } from './stack-utils'; @Directive({ selector: 'ion-tabs', @@ -28,7 +28,13 @@ export class IonTabs implements AfterContentInit, AfterContentChecked { @ViewChild('tabsInner', { read: ElementRef, static: true }) tabsInner: ElementRef; + /** + * Emitted before the tab view is changed. + */ @Output() ionTabsWillChange = new EventEmitter<{ tab: string }>(); + /** + * Emitted after the tab view is changed. + */ @Output() ionTabsDidChange = new EventEmitter<{ tab: string }>(); private tabBarSlot = 'bottom'; @@ -46,10 +52,19 @@ export class IonTabs implements AfterContentInit, AfterContentChecked { /** * @internal */ - onPageSelected(detail: StackEvent): void { - const stackId = detail.enteringView.stackId; - if (detail.tabSwitch && stackId !== undefined) { + onStackWillChange({ enteringView, tabSwitch }: StackWillChangeEvent): void { + const stackId = enteringView.stackId; + if (tabSwitch && stackId !== undefined) { this.ionTabsWillChange.emit({ tab: stackId }); + } + } + + /** + * @internal + */ + onStackDidChange({ enteringView, tabSwitch }: StackDidChangeEvent): void { + const stackId = enteringView.stackId; + if (tabSwitch && stackId !== undefined) { if (this.tabBar) { this.tabBar.selectedTab = stackId; } diff --git a/packages/angular/common/src/index.ts b/packages/angular/common/src/index.ts index cab9a8ea87d..a6f5edec378 100644 --- a/packages/angular/common/src/index.ts +++ b/packages/angular/common/src/index.ts @@ -25,7 +25,7 @@ export { IonPopover } from './overlays/popover'; export { IonModal } from './overlays/modal'; export { IonRouterOutlet, provideComponentInputBinding } from './directives/navigation/router-outlet'; -export type { StackEvent } from './directives/navigation/stack-utils'; +export type { StackWillChangeEvent } from './directives/navigation/stack-utils'; export { IonBackButton } from './directives/navigation/back-button'; export { diff --git a/packages/angular/standalone/src/navigation/tabs.ts b/packages/angular/standalone/src/navigation/tabs.ts index b54be17934f..50c85f1593f 100644 --- a/packages/angular/standalone/src/navigation/tabs.ts +++ b/packages/angular/standalone/src/navigation/tabs.ts @@ -10,7 +10,12 @@ import { IonRouterOutlet } from './router-outlet'; template: `
- +
`, From 3720ae6a09cc67b0341a3ac5a3b0a44d34cc8c6a Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 7 Sep 2023 08:08:14 -0700 Subject: [PATCH 20/24] chore(angular): remove unused type --- packages/angular/common/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/angular/common/src/index.ts b/packages/angular/common/src/index.ts index a6f5edec378..e223fb98780 100644 --- a/packages/angular/common/src/index.ts +++ b/packages/angular/common/src/index.ts @@ -25,7 +25,6 @@ export { IonPopover } from './overlays/popover'; export { IonModal } from './overlays/modal'; export { IonRouterOutlet, provideComponentInputBinding } from './directives/navigation/router-outlet'; -export type { StackWillChangeEvent } from './directives/navigation/stack-utils'; export { IonBackButton } from './directives/navigation/back-button'; export { From dad7e66cf659357581d85f7b3d264df2128b63a8 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Tue, 12 Sep 2023 12:06:59 -0400 Subject: [PATCH 21/24] fix(angular): include core exports in standalone (#28158) --- packages/angular/standalone/src/index.ts | 86 ++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/packages/angular/standalone/src/index.ts b/packages/angular/standalone/src/index.ts index 8bd5de65c8b..15895b0fef0 100644 --- a/packages/angular/standalone/src/index.ts +++ b/packages/angular/standalone/src/index.ts @@ -26,3 +26,89 @@ export { export { IonNav } from './navigation/nav'; export { IonIcon } from './directives/icon'; export * from './directives/proxies'; + +export { + // UTILS + createAnimation, + createGesture, + iosTransitionAnimation, + mdTransitionAnimation, + IonicSlides, + getPlatforms, + isPlatform, + getTimeGivenProgression, + // TYPES + Animation, + AnimationBuilder, + AnimationCallbackOptions, + AnimationDirection, + AnimationFill, + AnimationKeyFrames, + AnimationLifecycle, + Gesture, + GestureConfig, + GestureDetail, + NavComponentWithProps, + SpinnerTypes, + AccordionGroupCustomEvent, + AccordionGroupChangeEventDetail, + BreadcrumbCustomEvent, + BreadcrumbCollapsedClickEventDetail, + ActionSheetOptions, + ActionSheetButton, + AlertOptions, + AlertInput, + AlertButton, + BackButtonEvent, + CheckboxCustomEvent, + CheckboxChangeEventDetail, + DatetimeCustomEvent, + DatetimeChangeEventDetail, + InfiniteScrollCustomEvent, + InputCustomEvent, + InputChangeEventDetail, + ItemReorderEventDetail, + ItemReorderCustomEvent, + ItemSlidingCustomEvent, + IonicSafeString, + LoadingOptions, + MenuCustomEvent, + ModalOptions, + NavCustomEvent, + PickerOptions, + PickerButton, + PickerColumn, + PickerColumnOption, + PlatformConfig, + PopoverOptions, + RadioGroupCustomEvent, + RadioGroupChangeEventDetail, + RangeCustomEvent, + RangeChangeEventDetail, + RangeKnobMoveStartEventDetail, + RangeKnobMoveEndEventDetail, + RefresherCustomEvent, + RefresherEventDetail, + RouterEventDetail, + RouterCustomEvent, + ScrollBaseCustomEvent, + ScrollBaseDetail, + ScrollDetail, + ScrollCustomEvent, + SearchbarCustomEvent, + SearchbarChangeEventDetail, + SearchbarInputEventDetail, + SegmentChangeEventDetail, + SegmentCustomEvent, + SegmentValue, + SelectChangeEventDetail, + SelectCustomEvent, + TabsCustomEvent, + TextareaChangeEventDetail, + TextareaCustomEvent, + ToastOptions, + ToastButton, + ToastLayout, + ToggleChangeEventDetail, + ToggleCustomEvent, +} from '@ionic/core/components'; From 28f2ec9c62fc301b432a7bca9a985b8ce90ef843 Mon Sep 17 00:00:00 2001 From: Sean Perkins <13732623+sean-perkins@users.noreply.github.com> Date: Tue, 19 Sep 2023 12:46:28 -0400 Subject: [PATCH 22/24] feat(angular): standalone form controls can participate in forms (#28125) Issue number: N/A --------- ## What is the current behavior? Ionic standalone form controls cannot participate in Angular forms. ## What is the new behavior? - Ionic form controls can participate in Angular forms by importing the standalone component - Applies to: `ion-input`, `ion-textarea`, `ion-searchbar`, `ion-toggle`, `ion-checkbox`, `ion-segment`, `ion-radio`, `ion-radio-group`, `ion-datetime` and `ion-range`. - Refactors `ValueAccessor` from `@ionic/angular` to `@ionic/angular/common` - Refactors `raf` utility from `@ionic/angular` to `@ionic/angular/common` ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information --------- --- core/stencil.config.ts | 17 +- .../control-value-accessors/index.ts | 1 + .../control-value-accessors/value-accessor.ts | 24 +- packages/angular/common/src/index.ts | 3 + .../{src/util => common/src/utils}/util.ts | 0 packages/angular/src/app-initialize.ts | 3 +- .../boolean-value-accessor.ts | 7 +- .../numeric-value-accessor.ts | 3 +- .../radio-value-accessor.ts | 8 +- .../select-value-accessor.ts | 3 +- .../text-value-accessor.ts | 3 +- .../standalone/src/directives/checkbox.ts | 86 ++ .../standalone/src/directives/datetime.ts | 103 +++ .../standalone/src/directives/index.ts | 12 + .../standalone/src/directives/input.ts | 145 +++ .../standalone/src/directives/proxies.ts | 873 ------------------ .../standalone/src/directives/radio-group.ts | 59 ++ .../standalone/src/directives/radio.ts | 67 ++ .../standalone/src/directives/range.ts | 112 +++ .../standalone/src/directives/searchbar.ts | 108 +++ .../standalone/src/directives/segment.ts | 60 ++ .../standalone/src/directives/select.ts | 98 ++ .../standalone/src/directives/textarea.ts | 110 +++ .../standalone/src/directives/toggle.ts | 84 ++ packages/angular/standalone/src/index.ts | 15 +- .../src/standalone/value-accessors.spec.ts | 132 +++ .../standalone/app-standalone/app.routes.ts | 15 + .../checkbox/checkbox.component.html | 11 + .../checkbox/checkbox.component.ts | 25 + .../datetime/datetime.component.html | 11 + .../datetime/datetime.component.ts | 25 + .../input/input.component.html | 11 + .../value-accessors/input/input.component.ts | 26 + .../radio-group/radio-group.component.html | 14 + .../radio-group/radio-group.component.ts | 26 + .../range/range.component.html | 9 + .../value-accessors/range/range.component.ts | 25 + .../searchbar/searchbar.component.html | 10 + .../searchbar/searchbar.component.ts | 25 + .../segment/segment.component.html | 18 + .../segment/segment.component.ts | 27 + .../textarea/textarea.component.html | 11 + .../textarea/textarea.component.ts | 25 + .../toggle/toggle.component.html | 10 + .../toggle/toggle.component.ts | 25 + .../value-accessor-test.component.html | 16 + .../value-accessor-test.component.ts | 27 + 47 files changed, 1626 insertions(+), 902 deletions(-) create mode 100644 packages/angular/common/src/directives/control-value-accessors/index.ts rename packages/angular/{ => common}/src/directives/control-value-accessors/value-accessor.ts (87%) rename packages/angular/{src/util => common/src/utils}/util.ts (100%) create mode 100644 packages/angular/standalone/src/directives/checkbox.ts create mode 100644 packages/angular/standalone/src/directives/datetime.ts create mode 100644 packages/angular/standalone/src/directives/index.ts create mode 100644 packages/angular/standalone/src/directives/input.ts create mode 100644 packages/angular/standalone/src/directives/radio-group.ts create mode 100644 packages/angular/standalone/src/directives/radio.ts create mode 100644 packages/angular/standalone/src/directives/range.ts create mode 100644 packages/angular/standalone/src/directives/searchbar.ts create mode 100644 packages/angular/standalone/src/directives/segment.ts create mode 100644 packages/angular/standalone/src/directives/select.ts create mode 100644 packages/angular/standalone/src/directives/textarea.ts create mode 100644 packages/angular/standalone/src/directives/toggle.ts create mode 100644 packages/angular/test/base/e2e/src/standalone/value-accessors.spec.ts create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/checkbox/checkbox.component.html create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/checkbox/checkbox.component.ts create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/datetime/datetime.component.html create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/datetime/datetime.component.ts create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/input/input.component.html create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/input/input.component.ts create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/radio-group/radio-group.component.html create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/radio-group/radio-group.component.ts create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/range/range.component.html create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/range/range.component.ts create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/searchbar/searchbar.component.html create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/searchbar/searchbar.component.ts create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/segment/segment.component.html create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/segment/segment.component.ts create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/textarea/textarea.component.html create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/textarea/textarea.component.ts create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/toggle/toggle.component.html create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/toggle/toggle.component.ts create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/value-accessor-test/value-accessor-test.component.html create mode 100644 packages/angular/test/base/src/app/standalone/value-accessors/value-accessor-test/value-accessor-test.component.ts diff --git a/core/stencil.config.ts b/core/stencil.config.ts index 300a8fd6347..0107c8f76b7 100644 --- a/core/stencil.config.ts +++ b/core/stencil.config.ts @@ -50,7 +50,22 @@ const getAngularOutputTargets = () => { * are reliant on the CE build will reference the wrong * import location. */ - 'ion-icon' + 'ion-icon', + /** + * Value Accessors are manually implemented in the `@ionic/angular/standalone` package. + */ + 'ion-input', + 'ion-textarea', + 'ion-searchbar', + 'ion-datetime', + 'ion-radio', + 'ion-segment', + 'ion-checkbox', + 'ion-toggle', + 'ion-range', + 'ion-radio-group', + 'ion-select' + ], outputType: 'standalone', }) diff --git a/packages/angular/common/src/directives/control-value-accessors/index.ts b/packages/angular/common/src/directives/control-value-accessors/index.ts new file mode 100644 index 00000000000..e00caae1aca --- /dev/null +++ b/packages/angular/common/src/directives/control-value-accessors/index.ts @@ -0,0 +1 @@ +export * from './value-accessor'; diff --git a/packages/angular/src/directives/control-value-accessors/value-accessor.ts b/packages/angular/common/src/directives/control-value-accessors/value-accessor.ts similarity index 87% rename from packages/angular/src/directives/control-value-accessors/value-accessor.ts rename to packages/angular/common/src/directives/control-value-accessors/value-accessor.ts index 67042a1a2d1..9c4dc7444bc 100644 --- a/packages/angular/src/directives/control-value-accessors/value-accessor.ts +++ b/packages/angular/common/src/directives/control-value-accessors/value-accessor.ts @@ -2,7 +2,7 @@ import { AfterViewInit, ElementRef, Injector, OnDestroy, Directive, HostListener import { ControlValueAccessor, NgControl } from '@angular/forms'; import { Subscription } from 'rxjs'; -import { raf } from '../../util/util'; +import { raf } from '../../utils/util'; // TODO(FW-2827): types @@ -17,11 +17,11 @@ export class ValueAccessor implements ControlValueAccessor, AfterViewInit, OnDes protected lastValue: any; private statusChanges?: Subscription; - constructor(protected injector: Injector, protected el: ElementRef) {} + constructor(protected injector: Injector, protected elementRef: ElementRef) {} writeValue(value: any): void { - this.el.nativeElement.value = this.lastValue = value; - setIonicClasses(this.el); + this.elementRef.nativeElement.value = this.lastValue = value; + setIonicClasses(this.elementRef); } /** @@ -38,20 +38,20 @@ export class ValueAccessor implements ControlValueAccessor, AfterViewInit, OnDes * @param value The new value of the control. */ handleValueChange(el: HTMLElement, value: any): void { - if (el === this.el.nativeElement) { + if (el === this.elementRef.nativeElement) { if (value !== this.lastValue) { this.lastValue = value; this.onChange(value); } - setIonicClasses(this.el); + setIonicClasses(this.elementRef); } } @HostListener('ionBlur', ['$event.target']) _handleBlurEvent(el: any): void { - if (el === this.el.nativeElement) { + if (el === this.elementRef.nativeElement) { this.onTouched(); - setIonicClasses(this.el); + setIonicClasses(this.elementRef); } } @@ -64,7 +64,7 @@ export class ValueAccessor implements ControlValueAccessor, AfterViewInit, OnDes } setDisabledState(isDisabled: boolean): void { - this.el.nativeElement.disabled = isDisabled; + this.elementRef.nativeElement.disabled = isDisabled; } ngOnDestroy(): void { @@ -87,7 +87,7 @@ export class ValueAccessor implements ControlValueAccessor, AfterViewInit, OnDes // Listen for changes in validity, disabled, or pending states if (ngControl.statusChanges) { - this.statusChanges = ngControl.statusChanges.subscribe(() => setIonicClasses(this.el)); + this.statusChanges = ngControl.statusChanges.subscribe(() => setIonicClasses(this.elementRef)); } /** @@ -102,7 +102,7 @@ export class ValueAccessor implements ControlValueAccessor, AfterViewInit, OnDes const oldFn = formControl[method].bind(formControl); formControl[method] = (...params: any[]) => { oldFn(...params); - setIonicClasses(this.el); + setIonicClasses(this.elementRef); }; } }); @@ -129,7 +129,7 @@ export const setIonicClasses = (element: ElementRef): void => { const getClasses = (element: HTMLElement) => { const classList = element.classList; - const classes = []; + const classes: string[] = []; for (let i = 0; i < classList.length; i++) { const item = classList.item(i); if (item !== null && startsWith(item, 'ng-')) { diff --git a/packages/angular/common/src/index.ts b/packages/angular/common/src/index.ts index e223fb98780..7b834ffe14a 100644 --- a/packages/angular/common/src/index.ts +++ b/packages/angular/common/src/index.ts @@ -33,7 +33,10 @@ export { } from './directives/navigation/router-link-delegate'; export { IonNav } from './directives/navigation/nav'; export { IonTabs } from './directives/navigation/tabs'; +export * from './directives/control-value-accessors'; export { ProxyCmp } from './utils/proxy'; export { IonicRouteStrategy } from './utils/routing'; + +export { raf } from './utils/util'; diff --git a/packages/angular/src/util/util.ts b/packages/angular/common/src/utils/util.ts similarity index 100% rename from packages/angular/src/util/util.ts rename to packages/angular/common/src/utils/util.ts diff --git a/packages/angular/src/app-initialize.ts b/packages/angular/src/app-initialize.ts index 557777eaf6a..69cf4030f43 100644 --- a/packages/angular/src/app-initialize.ts +++ b/packages/angular/src/app-initialize.ts @@ -1,10 +1,9 @@ import { NgZone } from '@angular/core'; import type { Config, IonicWindow } from '@ionic/angular/common'; +import { raf } from '@ionic/angular/common'; import { setupConfig } from '@ionic/core'; import { applyPolyfills, defineCustomElements } from '@ionic/core/loader'; -import { raf } from './util/util'; - // TODO(FW-2827): types export const appInitialize = (config: Config, doc: Document, zone: NgZone) => { diff --git a/packages/angular/src/directives/control-value-accessors/boolean-value-accessor.ts b/packages/angular/src/directives/control-value-accessors/boolean-value-accessor.ts index be5f8d276c1..9c4332fc976 100644 --- a/packages/angular/src/directives/control-value-accessors/boolean-value-accessor.ts +++ b/packages/angular/src/directives/control-value-accessors/boolean-value-accessor.ts @@ -1,7 +1,6 @@ import { Directive, HostListener, ElementRef, Injector } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; - -import { ValueAccessor, setIonicClasses } from './value-accessor'; +import { ValueAccessor, setIonicClasses } from '@ionic/angular/common'; @Directive({ selector: 'ion-checkbox,ion-toggle', @@ -19,8 +18,8 @@ export class BooleanValueAccessorDirective extends ValueAccessor { } writeValue(value: boolean): void { - this.el.nativeElement.checked = this.lastValue = value; - setIonicClasses(this.el); + this.elementRef.nativeElement.checked = this.lastValue = value; + setIonicClasses(this.elementRef); } @HostListener('ionChange', ['$event.target']) diff --git a/packages/angular/src/directives/control-value-accessors/numeric-value-accessor.ts b/packages/angular/src/directives/control-value-accessors/numeric-value-accessor.ts index 33401ee0e19..e63a91f3c7c 100644 --- a/packages/angular/src/directives/control-value-accessors/numeric-value-accessor.ts +++ b/packages/angular/src/directives/control-value-accessors/numeric-value-accessor.ts @@ -1,7 +1,6 @@ import { Directive, HostListener, ElementRef, Injector } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; - -import { ValueAccessor } from './value-accessor'; +import { ValueAccessor } from '@ionic/angular/common'; @Directive({ selector: 'ion-input[type=number]', diff --git a/packages/angular/src/directives/control-value-accessors/radio-value-accessor.ts b/packages/angular/src/directives/control-value-accessors/radio-value-accessor.ts index 9c120a6bdfd..014a2cf1c04 100644 --- a/packages/angular/src/directives/control-value-accessors/radio-value-accessor.ts +++ b/packages/angular/src/directives/control-value-accessors/radio-value-accessor.ts @@ -1,7 +1,6 @@ import { ElementRef, Injector, Directive, HostListener } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; - -import { ValueAccessor } from './value-accessor'; +import { ValueAccessor } from '@ionic/angular/common'; @Directive({ /* tslint:disable-next-line:directive-selector */ @@ -19,9 +18,12 @@ export class RadioValueAccessorDirective extends ValueAccessor { super(injector, el); } - // TODO(FW-2827): type (HTMLIonRadioElement and HTMLElement are both missing `checked`) @HostListener('ionSelect', ['$event.target']) _handleIonSelect(el: any): void { + /** + * The `el` type is any to access the `checked` state property + * that is not exposed on the type interface. + */ this.handleValueChange(el, el.checked); } } diff --git a/packages/angular/src/directives/control-value-accessors/select-value-accessor.ts b/packages/angular/src/directives/control-value-accessors/select-value-accessor.ts index ad3ec60e52b..7e2683edaf3 100644 --- a/packages/angular/src/directives/control-value-accessors/select-value-accessor.ts +++ b/packages/angular/src/directives/control-value-accessors/select-value-accessor.ts @@ -1,7 +1,6 @@ import { ElementRef, Injector, Directive, HostListener } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; - -import { ValueAccessor } from './value-accessor'; +import { ValueAccessor } from '@ionic/angular/common'; @Directive({ /* tslint:disable-next-line:directive-selector */ diff --git a/packages/angular/src/directives/control-value-accessors/text-value-accessor.ts b/packages/angular/src/directives/control-value-accessors/text-value-accessor.ts index aec3447db26..f4b52198cf5 100644 --- a/packages/angular/src/directives/control-value-accessors/text-value-accessor.ts +++ b/packages/angular/src/directives/control-value-accessors/text-value-accessor.ts @@ -1,7 +1,6 @@ import { ElementRef, Injector, Directive, HostListener } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; - -import { ValueAccessor } from './value-accessor'; +import { ValueAccessor } from '@ionic/angular/common'; @Directive({ selector: 'ion-input:not([type=number]),ion-textarea,ion-searchbar', diff --git a/packages/angular/standalone/src/directives/checkbox.ts b/packages/angular/standalone/src/directives/checkbox.ts new file mode 100644 index 00000000000..26f139f379b --- /dev/null +++ b/packages/angular/standalone/src/directives/checkbox.ts @@ -0,0 +1,86 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + HostListener, + Injector, + NgZone, +} from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ValueAccessor, setIonicClasses } from '@ionic/angular/common'; +import type { CheckboxChangeEventDetail, Components } from '@ionic/core/components'; +import { defineCustomElement } from '@ionic/core/components/ion-checkbox.js'; + +import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; + +const CHECKBOX_INPUTS = [ + 'checked', + 'color', + 'disabled', + 'indeterminate', + 'justify', + 'labelPlacement', + 'legacy', + 'mode', + 'name', + 'value', +]; + +@ProxyCmp({ + defineCustomElementFn: defineCustomElement, + inputs: CHECKBOX_INPUTS, +}) +@Component({ + selector: 'ion-checkbox', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: CHECKBOX_INPUTS, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: IonCheckbox, + multi: true, + }, + ], + standalone: true, +}) +export class IonCheckbox extends ValueAccessor { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) { + super(injector, r); + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionChange', 'ionFocus', 'ionBlur']); + } + + writeValue(value: boolean): void { + this.elementRef.nativeElement.checked = this.lastValue = value; + setIonicClasses(this.elementRef); + } + + @HostListener('ionChange', ['$event.target']) + handleIonChange(el: HTMLIonCheckboxElement | HTMLIonToggleElement): void { + this.handleValueChange(el, el.checked); + } +} + +export declare interface IonCheckbox extends Components.IonCheckbox { + /** + * Emitted when the checked property has changed +as a result of a user action such as a click. +This event will not emit when programmatically +setting the checked property. + */ + ionChange: EventEmitter>; + /** + * Emitted when the checkbox has focus. + */ + ionFocus: EventEmitter>; + /** + * Emitted when the checkbox loses focus. + */ + ionBlur: EventEmitter>; +} diff --git a/packages/angular/standalone/src/directives/datetime.ts b/packages/angular/standalone/src/directives/datetime.ts new file mode 100644 index 00000000000..57068f71b97 --- /dev/null +++ b/packages/angular/standalone/src/directives/datetime.ts @@ -0,0 +1,103 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + HostListener, + Injector, + NgZone, +} from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ValueAccessor } from '@ionic/angular/common'; +import type { DatetimeChangeEventDetail, Components } from '@ionic/core/components'; +import { defineCustomElement } from '@ionic/core/components/ion-datetime.js'; + +import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; + +const DATETIME_INPUTS = [ + 'cancelText', + 'clearText', + 'color', + 'dayValues', + 'disabled', + 'doneText', + 'firstDayOfWeek', + 'highlightedDates', + 'hourCycle', + 'hourValues', + 'isDateEnabled', + 'locale', + 'max', + 'min', + 'minuteValues', + 'mode', + 'monthValues', + 'multiple', + 'name', + 'preferWheel', + 'presentation', + 'readonly', + 'showClearButton', + 'showDefaultButtons', + 'showDefaultTimeLabel', + 'showDefaultTitle', + 'size', + 'titleSelectedDatesFormatter', + 'value', + 'yearValues', +]; + +@ProxyCmp({ + defineCustomElementFn: defineCustomElement, + inputs: DATETIME_INPUTS, + methods: ['confirm', 'reset', 'cancel'], +}) +@Component({ + selector: 'ion-datetime', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: DATETIME_INPUTS, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: IonDatetime, + multi: true, + }, + ], + standalone: true, +}) +export class IonDatetime extends ValueAccessor { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) { + super(injector, r); + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionCancel', 'ionChange', 'ionFocus', 'ionBlur']); + } + + @HostListener('ionChange', ['$event.target']) + handleIonChange(el: HTMLIonDatetimeElement): void { + this.handleValueChange(el, el.value); + } +} + +export declare interface IonDatetime extends Components.IonDatetime { + /** + * Emitted when the datetime selection was cancelled. + */ + ionCancel: EventEmitter>; + /** + * Emitted when the value (selected date) has changed. + */ + ionChange: EventEmitter>; + /** + * Emitted when the datetime has focus. + */ + ionFocus: EventEmitter>; + /** + * Emitted when the datetime loses focus. + */ + ionBlur: EventEmitter>; +} diff --git a/packages/angular/standalone/src/directives/index.ts b/packages/angular/standalone/src/directives/index.ts new file mode 100644 index 00000000000..8a2d8c4abe9 --- /dev/null +++ b/packages/angular/standalone/src/directives/index.ts @@ -0,0 +1,12 @@ +export * from './checkbox'; +export * from './datetime'; +export * from './icon'; +export * from './input'; +export * from './radio-group'; +export * from './radio'; +export * from './range'; +export * from './searchbar'; +export * from './segment'; +export * from './select'; +export * from './textarea'; +export * from './toggle'; diff --git a/packages/angular/standalone/src/directives/input.ts b/packages/angular/standalone/src/directives/input.ts new file mode 100644 index 00000000000..1fb55006f7e --- /dev/null +++ b/packages/angular/standalone/src/directives/input.ts @@ -0,0 +1,145 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + HostListener, + Injector, + NgZone, +} from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ValueAccessor } from '@ionic/angular/common'; +import type { + InputInputEventDetail as IIonInputInputInputEventDetail, + InputChangeEventDetail as IIonInputInputChangeEventDetail, + Components, +} from '@ionic/core/components'; +import { defineCustomElement } from '@ionic/core/components/ion-input.js'; + +import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; + +const INPUT_INPUTS = [ + 'accept', + 'autocapitalize', + 'autocomplete', + 'autocorrect', + 'autofocus', + 'clearInput', + 'clearOnEdit', + 'color', + 'counter', + 'counterFormatter', + 'debounce', + 'disabled', + 'enterkeyhint', + 'errorText', + 'fill', + 'helperText', + 'inputmode', + 'label', + 'labelPlacement', + 'legacy', + 'max', + 'maxlength', + 'min', + 'minlength', + 'mode', + 'multiple', + 'name', + 'pattern', + 'placeholder', + 'readonly', + 'required', + 'shape', + 'size', + 'spellcheck', + 'step', + 'type', + 'value', +]; + +@ProxyCmp({ + defineCustomElementFn: defineCustomElement, + inputs: INPUT_INPUTS, + methods: ['setFocus', 'getInputElement'], +}) +@Component({ + selector: 'ion-input', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: INPUT_INPUTS, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: IonInput, + multi: true, + }, + ], + standalone: true, +}) +export class IonInput extends ValueAccessor { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) { + super(injector, r); + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionInput', 'ionChange', 'ionBlur', 'ionFocus']); + } + + @HostListener('ionInput', ['$event.target']) + handleIonInput(el: HTMLIonInputElement): void { + this.handleValueChange(el, el.value); + } + + registerOnChange(fn: (_: any) => void): void { + super.registerOnChange((value: string) => { + if (this.type === 'number') { + /** + * If the input type is `number`, we need to convert the value to a number + * when the value is not empty. If the value is empty, we want to treat + * the value as null. + */ + fn(value === '' ? null : parseFloat(value)); + } else { + fn(value); + } + }); + } +} + +export declare interface IonInput extends Components.IonInput { + /** + * The `ionInput` event is fired each time the user modifies the input's value. +Unlike the `ionChange` event, the `ionInput` event is fired for each alteration +to the input's value. This typically happens for each keystroke as the user types. + +For elements that accept text input (`type=text`, `type=tel`, etc.), the interface +is [`InputEvent`](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent); for others, +the interface is [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event). If +the input is cleared on edit, the type is `null`. + */ + ionInput: EventEmitter>; + /** + * The `ionChange` event is fired when the user modifies the input's value. +Unlike the `ionInput` event, the `ionChange` event is only fired when changes +are committed, not as the user types. + +Depending on the way the users interacts with the element, the `ionChange` +event fires at a different moment: +- When the user commits the change explicitly (e.g. by selecting a date +from a date picker for ``, pressing the "Enter" key, etc.). +- When the element loses focus after its value has changed: for elements +where the user's interaction is typing. + */ + ionChange: EventEmitter>; + /** + * Emitted when the input loses focus. + */ + ionBlur: EventEmitter>; + /** + * Emitted when the input has focus. + */ + ionFocus: EventEmitter>; +} diff --git a/packages/angular/standalone/src/directives/proxies.ts b/packages/angular/standalone/src/directives/proxies.ts index dde743002da..d112c3b93ce 100644 --- a/packages/angular/standalone/src/directives/proxies.ts +++ b/packages/angular/standalone/src/directives/proxies.ts @@ -23,11 +23,9 @@ import { defineCustomElement as defineIonCardContent } from '@ionic/core/compone import { defineCustomElement as defineIonCardHeader } from '@ionic/core/components/ion-card-header.js'; import { defineCustomElement as defineIonCardSubtitle } from '@ionic/core/components/ion-card-subtitle.js'; import { defineCustomElement as defineIonCardTitle } from '@ionic/core/components/ion-card-title.js'; -import { defineCustomElement as defineIonCheckbox } from '@ionic/core/components/ion-checkbox.js'; import { defineCustomElement as defineIonChip } from '@ionic/core/components/ion-chip.js'; import { defineCustomElement as defineIonCol } from '@ionic/core/components/ion-col.js'; import { defineCustomElement as defineIonContent } from '@ionic/core/components/ion-content.js'; -import { defineCustomElement as defineIonDatetime } from '@ionic/core/components/ion-datetime.js'; import { defineCustomElement as defineIonDatetimeButton } from '@ionic/core/components/ion-datetime-button.js'; import { defineCustomElement as defineIonFab } from '@ionic/core/components/ion-fab.js'; import { defineCustomElement as defineIonFabButton } from '@ionic/core/components/ion-fab-button.js'; @@ -38,7 +36,6 @@ import { defineCustomElement as defineIonHeader } from '@ionic/core/components/i import { defineCustomElement as defineIonImg } from '@ionic/core/components/ion-img.js'; import { defineCustomElement as defineIonInfiniteScroll } from '@ionic/core/components/ion-infinite-scroll.js'; import { defineCustomElement as defineIonInfiniteScrollContent } from '@ionic/core/components/ion-infinite-scroll-content.js'; -import { defineCustomElement as defineIonInput } from '@ionic/core/components/ion-input.js'; import { defineCustomElement as defineIonItem } from '@ionic/core/components/ion-item.js'; import { defineCustomElement as defineIonItemDivider } from '@ionic/core/components/ion-item-divider.js'; import { defineCustomElement as defineIonItemGroup } from '@ionic/core/components/ion-item-group.js'; @@ -56,19 +53,13 @@ import { defineCustomElement as defineIonNavLink } from '@ionic/core/components/ import { defineCustomElement as defineIonNote } from '@ionic/core/components/ion-note.js'; import { defineCustomElement as defineIonPicker } from '@ionic/core/components/ion-picker.js'; import { defineCustomElement as defineIonProgressBar } from '@ionic/core/components/ion-progress-bar.js'; -import { defineCustomElement as defineIonRadio } from '@ionic/core/components/ion-radio.js'; -import { defineCustomElement as defineIonRadioGroup } from '@ionic/core/components/ion-radio-group.js'; -import { defineCustomElement as defineIonRange } from '@ionic/core/components/ion-range.js'; import { defineCustomElement as defineIonRefresher } from '@ionic/core/components/ion-refresher.js'; import { defineCustomElement as defineIonRefresherContent } from '@ionic/core/components/ion-refresher-content.js'; import { defineCustomElement as defineIonReorder } from '@ionic/core/components/ion-reorder.js'; import { defineCustomElement as defineIonReorderGroup } from '@ionic/core/components/ion-reorder-group.js'; import { defineCustomElement as defineIonRippleEffect } from '@ionic/core/components/ion-ripple-effect.js'; import { defineCustomElement as defineIonRow } from '@ionic/core/components/ion-row.js'; -import { defineCustomElement as defineIonSearchbar } from '@ionic/core/components/ion-searchbar.js'; -import { defineCustomElement as defineIonSegment } from '@ionic/core/components/ion-segment.js'; import { defineCustomElement as defineIonSegmentButton } from '@ionic/core/components/ion-segment-button.js'; -import { defineCustomElement as defineIonSelect } from '@ionic/core/components/ion-select.js'; import { defineCustomElement as defineIonSelectOption } from '@ionic/core/components/ion-select-option.js'; import { defineCustomElement as defineIonSkeletonText } from '@ionic/core/components/ion-skeleton-text.js'; import { defineCustomElement as defineIonSpinner } from '@ionic/core/components/ion-spinner.js'; @@ -76,11 +67,9 @@ import { defineCustomElement as defineIonSplitPane } from '@ionic/core/component import { defineCustomElement as defineIonTabBar } from '@ionic/core/components/ion-tab-bar.js'; import { defineCustomElement as defineIonTabButton } from '@ionic/core/components/ion-tab-button.js'; import { defineCustomElement as defineIonText } from '@ionic/core/components/ion-text.js'; -import { defineCustomElement as defineIonTextarea } from '@ionic/core/components/ion-textarea.js'; import { defineCustomElement as defineIonThumbnail } from '@ionic/core/components/ion-thumbnail.js'; import { defineCustomElement as defineIonTitle } from '@ionic/core/components/ion-title.js'; import { defineCustomElement as defineIonToast } from '@ionic/core/components/ion-toast.js'; -import { defineCustomElement as defineIonToggle } from '@ionic/core/components/ion-toggle.js'; import { defineCustomElement as defineIonToolbar } from '@ionic/core/components/ion-toolbar.js'; @ProxyCmp({ defineCustomElementFn: defineIonAccordion, @@ -747,69 +736,6 @@ export class IonCardTitle { export declare interface IonCardTitle extends Components.IonCardTitle {} -@ProxyCmp({ - defineCustomElementFn: defineIonCheckbox, - inputs: [ - 'checked', - 'color', - 'disabled', - 'indeterminate', - 'justify', - 'labelPlacement', - 'legacy', - 'mode', - 'name', - 'value', - ], -}) -@Component({ - selector: 'ion-checkbox', - changeDetection: ChangeDetectionStrategy.OnPush, - template: '', - // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: [ - 'checked', - 'color', - 'disabled', - 'indeterminate', - 'justify', - 'labelPlacement', - 'legacy', - 'mode', - 'name', - 'value', - ], - standalone: true, -}) -export class IonCheckbox { - protected el: HTMLElement; - constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { - c.detach(); - this.el = r.nativeElement; - proxyOutputs(this, this.el, ['ionChange', 'ionFocus', 'ionBlur']); - } -} - -import type { CheckboxChangeEventDetail as IIonCheckboxCheckboxChangeEventDetail } from '@ionic/core/components'; - -export declare interface IonCheckbox extends Components.IonCheckbox { - /** - * Emitted when the checked property has changed -as a result of a user action such as a click. -This event will not emit when programmatically -setting the checked property. - */ - ionChange: EventEmitter>; - /** - * Emitted when the checkbox has focus. - */ - ionFocus: EventEmitter>; - /** - * Emitted when the checkbox loses focus. - */ - ionBlur: EventEmitter>; -} - @ProxyCmp({ defineCustomElementFn: defineIonChip, inputs: ['color', 'disabled', 'mode', 'outline'], @@ -947,111 +873,6 @@ Set `scrollEvents` to `true` to enable. ionScrollEnd: EventEmitter>; } -@ProxyCmp({ - defineCustomElementFn: defineIonDatetime, - inputs: [ - 'cancelText', - 'clearText', - 'color', - 'dayValues', - 'disabled', - 'doneText', - 'firstDayOfWeek', - 'highlightedDates', - 'hourCycle', - 'hourValues', - 'isDateEnabled', - 'locale', - 'max', - 'min', - 'minuteValues', - 'mode', - 'monthValues', - 'multiple', - 'name', - 'preferWheel', - 'presentation', - 'readonly', - 'showClearButton', - 'showDefaultButtons', - 'showDefaultTimeLabel', - 'showDefaultTitle', - 'size', - 'titleSelectedDatesFormatter', - 'value', - 'yearValues', - ], - methods: ['confirm', 'reset', 'cancel'], -}) -@Component({ - selector: 'ion-datetime', - changeDetection: ChangeDetectionStrategy.OnPush, - template: '', - // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: [ - 'cancelText', - 'clearText', - 'color', - 'dayValues', - 'disabled', - 'doneText', - 'firstDayOfWeek', - 'highlightedDates', - 'hourCycle', - 'hourValues', - 'isDateEnabled', - 'locale', - 'max', - 'min', - 'minuteValues', - 'mode', - 'monthValues', - 'multiple', - 'name', - 'preferWheel', - 'presentation', - 'readonly', - 'showClearButton', - 'showDefaultButtons', - 'showDefaultTimeLabel', - 'showDefaultTitle', - 'size', - 'titleSelectedDatesFormatter', - 'value', - 'yearValues', - ], - standalone: true, -}) -export class IonDatetime { - protected el: HTMLElement; - constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { - c.detach(); - this.el = r.nativeElement; - proxyOutputs(this, this.el, ['ionCancel', 'ionChange', 'ionFocus', 'ionBlur']); - } -} - -import type { DatetimeChangeEventDetail as IIonDatetimeDatetimeChangeEventDetail } from '@ionic/core/components'; - -export declare interface IonDatetime extends Components.IonDatetime { - /** - * Emitted when the datetime selection was cancelled. - */ - ionCancel: EventEmitter>; - /** - * Emitted when the value (selected date) has changed. - */ - ionChange: EventEmitter>; - /** - * Emitted when the datetime has focus. - */ - ionFocus: EventEmitter>; - /** - * Emitted when the datetime loses focus. - */ - ionBlur: EventEmitter>; -} - @ProxyCmp({ defineCustomElementFn: defineIonDatetimeButton, inputs: ['color', 'datetime', 'disabled', 'mode'], @@ -1339,142 +1160,6 @@ export class IonInfiniteScrollContent { export declare interface IonInfiniteScrollContent extends Components.IonInfiniteScrollContent {} -@ProxyCmp({ - defineCustomElementFn: defineIonInput, - inputs: [ - 'accept', - 'autocapitalize', - 'autocomplete', - 'autocorrect', - 'autofocus', - 'clearInput', - 'clearOnEdit', - 'color', - 'counter', - 'counterFormatter', - 'debounce', - 'disabled', - 'enterkeyhint', - 'errorText', - 'fill', - 'helperText', - 'inputmode', - 'label', - 'labelPlacement', - 'legacy', - 'max', - 'maxlength', - 'min', - 'minlength', - 'mode', - 'multiple', - 'name', - 'pattern', - 'placeholder', - 'readonly', - 'required', - 'shape', - 'size', - 'spellcheck', - 'step', - 'type', - 'value', - ], - methods: ['setFocus', 'getInputElement'], -}) -@Component({ - selector: 'ion-input', - changeDetection: ChangeDetectionStrategy.OnPush, - template: '', - // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: [ - 'accept', - 'autocapitalize', - 'autocomplete', - 'autocorrect', - 'autofocus', - 'clearInput', - 'clearOnEdit', - 'color', - 'counter', - 'counterFormatter', - 'debounce', - 'disabled', - 'enterkeyhint', - 'errorText', - 'fill', - 'helperText', - 'inputmode', - 'label', - 'labelPlacement', - 'legacy', - 'max', - 'maxlength', - 'min', - 'minlength', - 'mode', - 'multiple', - 'name', - 'pattern', - 'placeholder', - 'readonly', - 'required', - 'shape', - 'size', - 'spellcheck', - 'step', - 'type', - 'value', - ], - standalone: true, -}) -export class IonInput { - protected el: HTMLElement; - constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { - c.detach(); - this.el = r.nativeElement; - proxyOutputs(this, this.el, ['ionInput', 'ionChange', 'ionBlur', 'ionFocus']); - } -} - -import type { InputInputEventDetail as IIonInputInputInputEventDetail } from '@ionic/core/components'; -import type { InputChangeEventDetail as IIonInputInputChangeEventDetail } from '@ionic/core/components'; - -export declare interface IonInput extends Components.IonInput { - /** - * The `ionInput` event is fired each time the user modifies the input's value. -Unlike the `ionChange` event, the `ionInput` event is fired for each alteration -to the input's value. This typically happens for each keystroke as the user types. - -For elements that accept text input (`type=text`, `type=tel`, etc.), the interface -is [`InputEvent`](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent); for others, -the interface is [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event). If -the input is cleared on edit, the type is `null`. - */ - ionInput: EventEmitter>; - /** - * The `ionChange` event is fired when the user modifies the input's value. -Unlike the `ionInput` event, the `ionChange` event is only fired when changes -are committed, not as the user types. - -Depending on the way the users interacts with the element, the `ionChange` -event fires at a different moment: -- When the user commits the change explicitly (e.g. by selecting a date -from a date picker for ``, pressing the "Enter" key, etc.). -- When the element loses focus after its value has changed: for elements -where the user's interaction is typing. - */ - ionChange: EventEmitter>; - /** - * Emitted when the input loses focus. - */ - ionBlur: EventEmitter>; - /** - * Emitted when the input has focus. - */ - ionFocus: EventEmitter>; -} - @ProxyCmp({ defineCustomElementFn: defineIonItem, inputs: [ @@ -2081,167 +1766,6 @@ export class IonProgressBar { export declare interface IonProgressBar extends Components.IonProgressBar {} -@ProxyCmp({ - defineCustomElementFn: defineIonRadio, - inputs: ['color', 'disabled', 'justify', 'labelPlacement', 'legacy', 'mode', 'name', 'value'], -}) -@Component({ - selector: 'ion-radio', - changeDetection: ChangeDetectionStrategy.OnPush, - template: '', - // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'disabled', 'justify', 'labelPlacement', 'legacy', 'mode', 'name', 'value'], - standalone: true, -}) -export class IonRadio { - protected el: HTMLElement; - constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { - c.detach(); - this.el = r.nativeElement; - proxyOutputs(this, this.el, ['ionFocus', 'ionBlur']); - } -} - -export declare interface IonRadio extends Components.IonRadio { - /** - * Emitted when the radio button has focus. - */ - ionFocus: EventEmitter>; - /** - * Emitted when the radio button loses focus. - */ - ionBlur: EventEmitter>; -} - -@ProxyCmp({ - defineCustomElementFn: defineIonRadioGroup, - inputs: ['allowEmptySelection', 'name', 'value'], -}) -@Component({ - selector: 'ion-radio-group', - changeDetection: ChangeDetectionStrategy.OnPush, - template: '', - // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['allowEmptySelection', 'name', 'value'], - standalone: true, -}) -export class IonRadioGroup { - protected el: HTMLElement; - constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { - c.detach(); - this.el = r.nativeElement; - proxyOutputs(this, this.el, ['ionChange']); - } -} - -import type { RadioGroupChangeEventDetail as IIonRadioGroupRadioGroupChangeEventDetail } from '@ionic/core/components'; - -export declare interface IonRadioGroup extends Components.IonRadioGroup { - /** - * Emitted when the value has changed. - */ - ionChange: EventEmitter>; -} - -@ProxyCmp({ - defineCustomElementFn: defineIonRange, - inputs: [ - 'activeBarStart', - 'color', - 'debounce', - 'disabled', - 'dualKnobs', - 'label', - 'labelPlacement', - 'legacy', - 'max', - 'min', - 'mode', - 'name', - 'pin', - 'pinFormatter', - 'snaps', - 'step', - 'ticks', - 'value', - ], -}) -@Component({ - selector: 'ion-range', - changeDetection: ChangeDetectionStrategy.OnPush, - template: '', - // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: [ - 'activeBarStart', - 'color', - 'debounce', - 'disabled', - 'dualKnobs', - 'label', - 'labelPlacement', - 'legacy', - 'max', - 'min', - 'mode', - 'name', - 'pin', - 'pinFormatter', - 'snaps', - 'step', - 'ticks', - 'value', - ], - standalone: true, -}) -export class IonRange { - protected el: HTMLElement; - constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { - c.detach(); - this.el = r.nativeElement; - proxyOutputs(this, this.el, ['ionChange', 'ionInput', 'ionFocus', 'ionBlur', 'ionKnobMoveStart', 'ionKnobMoveEnd']); - } -} - -import type { RangeChangeEventDetail as IIonRangeRangeChangeEventDetail } from '@ionic/core/components'; -import type { RangeKnobMoveStartEventDetail as IIonRangeRangeKnobMoveStartEventDetail } from '@ionic/core/components'; -import type { RangeKnobMoveEndEventDetail as IIonRangeRangeKnobMoveEndEventDetail } from '@ionic/core/components'; - -export declare interface IonRange extends Components.IonRange { - /** - * The `ionChange` event is fired for `` elements when the user -modifies the element's value: -- When the user releases the knob after dragging; -- When the user moves the knob with keyboard arrows - -`ionChange` is not fired when the value is changed programmatically. - */ - ionChange: EventEmitter>; - /** - * The `ionInput` event is fired for `` elements when the value -is modified. Unlike `ionChange`, `ionInput` is fired continuously -while the user is dragging the knob. - */ - ionInput: EventEmitter>; - /** - * Emitted when the range has focus. - */ - ionFocus: EventEmitter>; - /** - * Emitted when the range loses focus. - */ - ionBlur: EventEmitter>; - /** - * Emitted when the user starts moving the range knob, whether through -mouse drag, touch gesture, or keyboard interaction. - */ - ionKnobMoveStart: EventEmitter>; - /** - * Emitted when the user finishes moving the range knob, whether through -mouse drag, touch gesture, or keyboard interaction. - */ - ionKnobMoveEnd: EventEmitter>; -} - @ProxyCmp({ defineCustomElementFn: defineIonRefresher, inputs: ['closeDuration', 'disabled', 'pullFactor', 'pullMax', 'pullMin', 'snapbackDuration'], @@ -2404,138 +1928,6 @@ export class IonRow { export declare interface IonRow extends Components.IonRow {} -@ProxyCmp({ - defineCustomElementFn: defineIonSearchbar, - inputs: [ - 'animated', - 'autocomplete', - 'autocorrect', - 'cancelButtonIcon', - 'cancelButtonText', - 'clearIcon', - 'color', - 'debounce', - 'disabled', - 'enterkeyhint', - 'inputmode', - 'mode', - 'name', - 'placeholder', - 'searchIcon', - 'showCancelButton', - 'showClearButton', - 'spellcheck', - 'type', - 'value', - ], - methods: ['setFocus', 'getInputElement'], -}) -@Component({ - selector: 'ion-searchbar', - changeDetection: ChangeDetectionStrategy.OnPush, - template: '', - // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: [ - 'animated', - 'autocomplete', - 'autocorrect', - 'cancelButtonIcon', - 'cancelButtonText', - 'clearIcon', - 'color', - 'debounce', - 'disabled', - 'enterkeyhint', - 'inputmode', - 'mode', - 'name', - 'placeholder', - 'searchIcon', - 'showCancelButton', - 'showClearButton', - 'spellcheck', - 'type', - 'value', - ], - standalone: true, -}) -export class IonSearchbar { - protected el: HTMLElement; - constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { - c.detach(); - this.el = r.nativeElement; - proxyOutputs(this, this.el, ['ionInput', 'ionChange', 'ionCancel', 'ionClear', 'ionBlur', 'ionFocus']); - } -} - -import type { SearchbarInputEventDetail as IIonSearchbarSearchbarInputEventDetail } from '@ionic/core/components'; -import type { SearchbarChangeEventDetail as IIonSearchbarSearchbarChangeEventDetail } from '@ionic/core/components'; - -export declare interface IonSearchbar extends Components.IonSearchbar { - /** - * Emitted when the `value` of the `ion-searchbar` element has changed. - */ - ionInput: EventEmitter>; - /** - * The `ionChange` event is fired for `` elements when the user -modifies the element's value. Unlike the `ionInput` event, the `ionChange` -event is not necessarily fired for each alteration to an element's value. - -The `ionChange` event is fired when the value has been committed -by the user. This can happen when the element loses focus or -when the "Enter" key is pressed. `ionChange` can also fire -when clicking the clear or cancel buttons. - */ - ionChange: EventEmitter>; - /** - * Emitted when the cancel button is clicked. - */ - ionCancel: EventEmitter>; - /** - * Emitted when the clear input button is clicked. - */ - ionClear: EventEmitter>; - /** - * Emitted when the input loses focus. - */ - ionBlur: EventEmitter>; - /** - * Emitted when the input has focus. - */ - ionFocus: EventEmitter>; -} - -@ProxyCmp({ - defineCustomElementFn: defineIonSegment, - inputs: ['color', 'disabled', 'mode', 'scrollable', 'selectOnFocus', 'swipeGesture', 'value'], -}) -@Component({ - selector: 'ion-segment', - changeDetection: ChangeDetectionStrategy.OnPush, - template: '', - // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'disabled', 'mode', 'scrollable', 'selectOnFocus', 'swipeGesture', 'value'], - standalone: true, -}) -export class IonSegment { - protected el: HTMLElement; - constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { - c.detach(); - this.el = r.nativeElement; - proxyOutputs(this, this.el, ['ionChange']); - } -} - -import type { SegmentChangeEventDetail as IIonSegmentSegmentChangeEventDetail } from '@ionic/core/components'; - -export declare interface IonSegment extends Components.IonSegment { - /** - * Emitted when the value property has changed and any -dragging pointer has been released from `ion-segment`. - */ - ionChange: EventEmitter>; -} - @ProxyCmp({ defineCustomElementFn: defineIonSegmentButton, inputs: ['disabled', 'layout', 'mode', 'type', 'value'], @@ -2558,97 +1950,6 @@ export class IonSegmentButton { export declare interface IonSegmentButton extends Components.IonSegmentButton {} -@ProxyCmp({ - defineCustomElementFn: defineIonSelect, - inputs: [ - 'cancelText', - 'color', - 'compareWith', - 'disabled', - 'expandedIcon', - 'fill', - 'interface', - 'interfaceOptions', - 'justify', - 'label', - 'labelPlacement', - 'legacy', - 'mode', - 'multiple', - 'name', - 'okText', - 'placeholder', - 'selectedText', - 'shape', - 'toggleIcon', - 'value', - ], - methods: ['open'], -}) -@Component({ - selector: 'ion-select', - changeDetection: ChangeDetectionStrategy.OnPush, - template: '', - // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: [ - 'cancelText', - 'color', - 'compareWith', - 'disabled', - 'expandedIcon', - 'fill', - 'interface', - 'interfaceOptions', - 'justify', - 'label', - 'labelPlacement', - 'legacy', - 'mode', - 'multiple', - 'name', - 'okText', - 'placeholder', - 'selectedText', - 'shape', - 'toggleIcon', - 'value', - ], - standalone: true, -}) -export class IonSelect { - protected el: HTMLElement; - constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { - c.detach(); - this.el = r.nativeElement; - proxyOutputs(this, this.el, ['ionChange', 'ionCancel', 'ionDismiss', 'ionFocus', 'ionBlur']); - } -} - -import type { SelectChangeEventDetail as IIonSelectSelectChangeEventDetail } from '@ionic/core/components'; - -export declare interface IonSelect extends Components.IonSelect { - /** - * Emitted when the value has changed. - */ - ionChange: EventEmitter>; - /** - * Emitted when the selection is cancelled. - */ - ionCancel: EventEmitter>; - /** - * Emitted when the overlay is dismissed. - */ - ionDismiss: EventEmitter>; - /** - * Emitted when the select has focus. - */ - ionFocus: EventEmitter>; - /** - * Emitted when the select loses focus. - */ - ionBlur: EventEmitter>; -} - @ProxyCmp({ defineCustomElementFn: defineIonSelectOption, inputs: ['disabled', 'value'], @@ -2809,119 +2110,6 @@ export class IonText { export declare interface IonText extends Components.IonText {} -@ProxyCmp({ - defineCustomElementFn: defineIonTextarea, - inputs: [ - 'autoGrow', - 'autocapitalize', - 'autofocus', - 'clearOnEdit', - 'color', - 'cols', - 'counter', - 'counterFormatter', - 'debounce', - 'disabled', - 'enterkeyhint', - 'errorText', - 'fill', - 'helperText', - 'inputmode', - 'label', - 'labelPlacement', - 'legacy', - 'maxlength', - 'minlength', - 'mode', - 'name', - 'placeholder', - 'readonly', - 'required', - 'rows', - 'shape', - 'spellcheck', - 'value', - 'wrap', - ], - methods: ['setFocus', 'getInputElement'], -}) -@Component({ - selector: 'ion-textarea', - changeDetection: ChangeDetectionStrategy.OnPush, - template: '', - // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: [ - 'autoGrow', - 'autocapitalize', - 'autofocus', - 'clearOnEdit', - 'color', - 'cols', - 'counter', - 'counterFormatter', - 'debounce', - 'disabled', - 'enterkeyhint', - 'errorText', - 'fill', - 'helperText', - 'inputmode', - 'label', - 'labelPlacement', - 'legacy', - 'maxlength', - 'minlength', - 'mode', - 'name', - 'placeholder', - 'readonly', - 'required', - 'rows', - 'shape', - 'spellcheck', - 'value', - 'wrap', - ], - standalone: true, -}) -export class IonTextarea { - protected el: HTMLElement; - constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { - c.detach(); - this.el = r.nativeElement; - proxyOutputs(this, this.el, ['ionChange', 'ionInput', 'ionBlur', 'ionFocus']); - } -} - -import type { TextareaChangeEventDetail as IIonTextareaTextareaChangeEventDetail } from '@ionic/core/components'; -import type { TextareaInputEventDetail as IIonTextareaTextareaInputEventDetail } from '@ionic/core/components'; - -export declare interface IonTextarea extends Components.IonTextarea { - /** - * The `ionChange` event is fired when the user modifies the textarea's value. -Unlike the `ionInput` event, the `ionChange` event is fired when -the element loses focus after its value has been modified. - */ - ionChange: EventEmitter>; - /** - * The `ionInput` event is fired each time the user modifies the textarea's value. -Unlike the `ionChange` event, the `ionInput` event is fired for each alteration -to the textarea's value. This typically happens for each keystroke as the user types. - -When `clearOnEdit` is enabled, the `ionInput` event will be fired when -the user clears the textarea by performing a keydown event. - */ - ionInput: EventEmitter>; - /** - * Emitted when the input loses focus. - */ - ionBlur: EventEmitter>; - /** - * Emitted when the input has focus. - */ - ionFocus: EventEmitter>; -} - @ProxyCmp({ defineCustomElementFn: defineIonThumbnail, }) @@ -3075,67 +2263,6 @@ Shorthand for ionToastDidDismiss. didDismiss: EventEmitter>; } -@ProxyCmp({ - defineCustomElementFn: defineIonToggle, - inputs: [ - 'checked', - 'color', - 'disabled', - 'enableOnOffLabels', - 'justify', - 'labelPlacement', - 'legacy', - 'mode', - 'name', - 'value', - ], -}) -@Component({ - selector: 'ion-toggle', - changeDetection: ChangeDetectionStrategy.OnPush, - template: '', - // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: [ - 'checked', - 'color', - 'disabled', - 'enableOnOffLabels', - 'justify', - 'labelPlacement', - 'legacy', - 'mode', - 'name', - 'value', - ], - standalone: true, -}) -export class IonToggle { - protected el: HTMLElement; - constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { - c.detach(); - this.el = r.nativeElement; - proxyOutputs(this, this.el, ['ionChange', 'ionFocus', 'ionBlur']); - } -} - -import type { ToggleChangeEventDetail as IIonToggleToggleChangeEventDetail } from '@ionic/core/components'; - -export declare interface IonToggle extends Components.IonToggle { - /** - * Emitted when the user switches the toggle on or off. Does not emit -when programmatically changing the value of the `checked` property. - */ - ionChange: EventEmitter>; - /** - * Emitted when the toggle has focus. - */ - ionFocus: EventEmitter>; - /** - * Emitted when the toggle loses focus. - */ - ionBlur: EventEmitter>; -} - @ProxyCmp({ defineCustomElementFn: defineIonToolbar, inputs: ['color', 'mode'], diff --git a/packages/angular/standalone/src/directives/radio-group.ts b/packages/angular/standalone/src/directives/radio-group.ts new file mode 100644 index 00000000000..998057a5e34 --- /dev/null +++ b/packages/angular/standalone/src/directives/radio-group.ts @@ -0,0 +1,59 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + HostListener, + Injector, + NgZone, +} from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ValueAccessor } from '@ionic/angular/common'; +import type { RadioGroupChangeEventDetail, Components } from '@ionic/core/components'; +import { defineCustomElement } from '@ionic/core/components/ion-radio-group.js'; + +import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; + +const RADIO_GROUP_INPUTS = ['allowEmptySelection', 'name', 'value']; + +@ProxyCmp({ + defineCustomElementFn: defineCustomElement, + inputs: RADIO_GROUP_INPUTS, +}) +@Component({ + selector: 'ion-radio-group', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: RADIO_GROUP_INPUTS, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: IonRadioGroup, + multi: true, + }, + ], + standalone: true, +}) +export class IonRadioGroup extends ValueAccessor { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) { + super(injector, r); + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionChange']); + } + + @HostListener('ionChange', ['$event.target']) + handleIonChange(el: HTMLIonRadioGroupElement): void { + this.handleValueChange(el, el.value); + } +} + +export declare interface IonRadioGroup extends Components.IonRadioGroup { + /** + * Emitted when the value has changed. + */ + ionChange: EventEmitter>; +} diff --git a/packages/angular/standalone/src/directives/radio.ts b/packages/angular/standalone/src/directives/radio.ts new file mode 100644 index 00000000000..9ad32fadf86 --- /dev/null +++ b/packages/angular/standalone/src/directives/radio.ts @@ -0,0 +1,67 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + HostListener, + Injector, + NgZone, +} from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ValueAccessor } from '@ionic/angular/common'; +import type { Components } from '@ionic/core/components'; +import { defineCustomElement } from '@ionic/core/components/ion-radio.js'; + +import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; + +const RADIO_INPUTS = ['color', 'disabled', 'justify', 'labelPlacement', 'legacy', 'mode', 'name', 'value']; + +@ProxyCmp({ + defineCustomElementFn: defineCustomElement, + inputs: RADIO_INPUTS, +}) +@Component({ + selector: 'ion-radio', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: RADIO_INPUTS, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: IonRadio, + multi: true, + }, + ], + standalone: true, +}) +export class IonRadio extends ValueAccessor { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) { + super(injector, r); + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionFocus', 'ionBlur']); + } + + @HostListener('ionSelect', ['$event.target']) + handleIonSelect(el: any): void { + /** + * The `el` type is any to access the `checked` state property + * that is not exposed on the type interface. + */ + this.handleValueChange(el, el.checked); + } +} + +export declare interface IonRadio extends Components.IonRadio { + /** + * Emitted when the radio button has focus. + */ + ionFocus: EventEmitter>; + /** + * Emitted when the radio button loses focus. + */ + ionBlur: EventEmitter>; +} diff --git a/packages/angular/standalone/src/directives/range.ts b/packages/angular/standalone/src/directives/range.ts new file mode 100644 index 00000000000..36aa4c93fca --- /dev/null +++ b/packages/angular/standalone/src/directives/range.ts @@ -0,0 +1,112 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + HostListener, + Injector, + NgZone, +} from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ValueAccessor } from '@ionic/angular/common'; +import type { + RangeChangeEventDetail, + RangeKnobMoveStartEventDetail, + RangeKnobMoveEndEventDetail, + Components, +} from '@ionic/core/components'; +import { defineCustomElement } from '@ionic/core/components/ion-range.js'; + +import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; + +const RANGE_INPUTS = [ + 'activeBarStart', + 'color', + 'debounce', + 'disabled', + 'dualKnobs', + 'label', + 'labelPlacement', + 'legacy', + 'max', + 'min', + 'mode', + 'name', + 'pin', + 'pinFormatter', + 'snaps', + 'step', + 'ticks', + 'value', +]; + +@ProxyCmp({ + defineCustomElementFn: defineCustomElement, + inputs: RANGE_INPUTS, +}) +@Component({ + selector: 'ion-range', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: RANGE_INPUTS, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: IonRange, + multi: true, + }, + ], + standalone: true, +}) +export class IonRange extends ValueAccessor { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) { + super(injector, r); + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionChange', 'ionInput', 'ionFocus', 'ionBlur', 'ionKnobMoveStart', 'ionKnobMoveEnd']); + } + + @HostListener('ionChange', ['$event.target']) + handleIonChange(el: HTMLIonRangeElement): void { + this.handleValueChange(el, el.value); + } +} + +export declare interface IonRange extends Components.IonRange { + /** + * The `ionChange` event is fired for `` elements when the user +modifies the element's value: +- When the user releases the knob after dragging; +- When the user moves the knob with keyboard arrows + +`ionChange` is not fired when the value is changed programmatically. + */ + ionChange: EventEmitter>; + /** + * The `ionInput` event is fired for `` elements when the value +is modified. Unlike `ionChange`, `ionInput` is fired continuously +while the user is dragging the knob. + */ + ionInput: EventEmitter>; + /** + * Emitted when the range has focus. + */ + ionFocus: EventEmitter>; + /** + * Emitted when the range loses focus. + */ + ionBlur: EventEmitter>; + /** + * Emitted when the user starts moving the range knob, whether through +mouse drag, touch gesture, or keyboard interaction. + */ + ionKnobMoveStart: EventEmitter>; + /** + * Emitted when the user finishes moving the range knob, whether through +mouse drag, touch gesture, or keyboard interaction. + */ + ionKnobMoveEnd: EventEmitter>; +} diff --git a/packages/angular/standalone/src/directives/searchbar.ts b/packages/angular/standalone/src/directives/searchbar.ts new file mode 100644 index 00000000000..5a4abd6d318 --- /dev/null +++ b/packages/angular/standalone/src/directives/searchbar.ts @@ -0,0 +1,108 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + HostListener, + Injector, + NgZone, +} from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ValueAccessor } from '@ionic/angular/common'; +import type { SearchbarInputEventDetail, SearchbarChangeEventDetail, Components } from '@ionic/core/components'; +import { defineCustomElement } from '@ionic/core/components/ion-searchbar.js'; + +import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; + +const SEARCHBAR_INPUTS = [ + 'animated', + 'autocomplete', + 'autocorrect', + 'cancelButtonIcon', + 'cancelButtonText', + 'clearIcon', + 'color', + 'debounce', + 'disabled', + 'enterkeyhint', + 'inputmode', + 'mode', + 'name', + 'placeholder', + 'searchIcon', + 'showCancelButton', + 'showClearButton', + 'spellcheck', + 'type', + 'value', +]; + +@ProxyCmp({ + defineCustomElementFn: defineCustomElement, + inputs: SEARCHBAR_INPUTS, + methods: ['setFocus', 'getInputElement'], +}) +@Component({ + selector: 'ion-searchbar', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: SEARCHBAR_INPUTS, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: IonSearchbar, + multi: true, + }, + ], + standalone: true, +}) +export class IonSearchbar extends ValueAccessor { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) { + super(injector, r); + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionInput', 'ionChange', 'ionCancel', 'ionClear', 'ionBlur', 'ionFocus']); + } + + @HostListener('ionInput', ['$event.target']) + handleIonInput(el: HTMLIonSearchbarElement): void { + this.handleValueChange(el, el.value); + } +} + +export declare interface IonSearchbar extends Components.IonSearchbar { + /** + * Emitted when the `value` of the `ion-searchbar` element has changed. + */ + ionInput: EventEmitter>; + /** + * The `ionChange` event is fired for `` elements when the user +modifies the element's value. Unlike the `ionInput` event, the `ionChange` +event is not necessarily fired for each alteration to an element's value. + +The `ionChange` event is fired when the value has been committed +by the user. This can happen when the element loses focus or +when the "Enter" key is pressed. `ionChange` can also fire +when clicking the clear or cancel buttons. + */ + ionChange: EventEmitter>; + /** + * Emitted when the cancel button is clicked. + */ + ionCancel: EventEmitter>; + /** + * Emitted when the clear input button is clicked. + */ + ionClear: EventEmitter>; + /** + * Emitted when the input loses focus. + */ + ionBlur: EventEmitter>; + /** + * Emitted when the input has focus. + */ + ionFocus: EventEmitter>; +} diff --git a/packages/angular/standalone/src/directives/segment.ts b/packages/angular/standalone/src/directives/segment.ts new file mode 100644 index 00000000000..760b990d5e5 --- /dev/null +++ b/packages/angular/standalone/src/directives/segment.ts @@ -0,0 +1,60 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + HostListener, + Injector, + NgZone, +} from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ValueAccessor } from '@ionic/angular/common'; +import type { SegmentChangeEventDetail, Components } from '@ionic/core/components'; +import { defineCustomElement } from '@ionic/core/components/ion-segment.js'; + +import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; + +const SEGMENT_INPUTS = ['color', 'disabled', 'mode', 'scrollable', 'selectOnFocus', 'swipeGesture', 'value']; + +@ProxyCmp({ + defineCustomElementFn: defineCustomElement, + inputs: SEGMENT_INPUTS, +}) +@Component({ + selector: 'ion-segment', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: SEGMENT_INPUTS, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: IonSegment, + multi: true, + }, + ], + standalone: true, +}) +export class IonSegment extends ValueAccessor { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) { + super(injector, r); + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionChange']); + } + + @HostListener('ionChange', ['$event.target']) + handleIonChange(el: HTMLIonSegmentElement): void { + this.handleValueChange(el, el.value); + } +} + +export declare interface IonSegment extends Components.IonSegment { + /** + * Emitted when the value property has changed and any +dragging pointer has been released from `ion-segment`. + */ + ionChange: EventEmitter>; +} diff --git a/packages/angular/standalone/src/directives/select.ts b/packages/angular/standalone/src/directives/select.ts new file mode 100644 index 00000000000..f0d19ce078e --- /dev/null +++ b/packages/angular/standalone/src/directives/select.ts @@ -0,0 +1,98 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + HostListener, + Injector, + NgZone, +} from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ValueAccessor } from '@ionic/angular/common'; +import type { SelectChangeEventDetail, Components } from '@ionic/core/components'; +import { defineCustomElement } from '@ionic/core/components/ion-select.js'; + +import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; + +const SELECT_INPUTS = [ + 'cancelText', + 'color', + 'compareWith', + 'disabled', + 'expandedIcon', + 'fill', + 'interface', + 'interfaceOptions', + 'justify', + 'label', + 'labelPlacement', + 'legacy', + 'mode', + 'multiple', + 'name', + 'okText', + 'placeholder', + 'selectedText', + 'shape', + 'toggleIcon', + 'value', +]; + +@ProxyCmp({ + defineCustomElementFn: defineCustomElement, + inputs: SELECT_INPUTS, + methods: ['open'], +}) +@Component({ + selector: 'ion-select', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: SELECT_INPUTS, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: IonSelect, + multi: true, + }, + ], + standalone: true, +}) +export class IonSelect extends ValueAccessor { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) { + super(injector, r); + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionChange', 'ionCancel', 'ionDismiss', 'ionFocus', 'ionBlur']); + } + + @HostListener('ionChange', ['$event.target']) + handleIonChange(el: HTMLIonSelectElement): void { + this.handleValueChange(el, el.value); + } +} + +export declare interface IonSelect extends Components.IonSelect { + /** + * Emitted when the value has changed. + */ + ionChange: EventEmitter>; + /** + * Emitted when the selection is cancelled. + */ + ionCancel: EventEmitter>; + /** + * Emitted when the overlay is dismissed. + */ + ionDismiss: EventEmitter>; + /** + * Emitted when the select has focus. + */ + ionFocus: EventEmitter>; + /** + * Emitted when the select loses focus. + */ + ionBlur: EventEmitter>; +} diff --git a/packages/angular/standalone/src/directives/textarea.ts b/packages/angular/standalone/src/directives/textarea.ts new file mode 100644 index 00000000000..75239632320 --- /dev/null +++ b/packages/angular/standalone/src/directives/textarea.ts @@ -0,0 +1,110 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + HostListener, + Injector, + NgZone, +} from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ValueAccessor } from '@ionic/angular/common'; +import type { TextareaChangeEventDetail, TextareaInputEventDetail, Components } from '@ionic/core/components'; +import { defineCustomElement } from '@ionic/core/components/ion-textarea.js'; + +import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; + +const TEXTAREA_INPUTS = [ + 'autoGrow', + 'autocapitalize', + 'autofocus', + 'clearOnEdit', + 'color', + 'cols', + 'counter', + 'counterFormatter', + 'debounce', + 'disabled', + 'enterkeyhint', + 'errorText', + 'fill', + 'helperText', + 'inputmode', + 'label', + 'labelPlacement', + 'legacy', + 'maxlength', + 'minlength', + 'mode', + 'name', + 'placeholder', + 'readonly', + 'required', + 'rows', + 'shape', + 'spellcheck', + 'value', + 'wrap', +]; + +@ProxyCmp({ + defineCustomElementFn: defineCustomElement, + inputs: TEXTAREA_INPUTS, + methods: ['setFocus', 'getInputElement'], +}) +@Component({ + selector: 'ion-textarea', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: TEXTAREA_INPUTS, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: IonTextarea, + multi: true, + }, + ], + standalone: true, +}) +export class IonTextarea extends ValueAccessor { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) { + super(injector, r); + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionChange', 'ionInput', 'ionBlur', 'ionFocus']); + } + + @HostListener('ionInput', ['$event.target']) + handleIonInput(el: HTMLIonTextareaElement): void { + this.handleValueChange(el, el.value); + } +} + +export declare interface IonTextarea extends Components.IonTextarea { + /** + * The `ionChange` event is fired when the user modifies the textarea's value. +Unlike the `ionInput` event, the `ionChange` event is fired when +the element loses focus after its value has been modified. + */ + ionChange: EventEmitter>; + /** + * The `ionInput` event is fired each time the user modifies the textarea's value. +Unlike the `ionChange` event, the `ionInput` event is fired for each alteration +to the textarea's value. This typically happens for each keystroke as the user types. + +When `clearOnEdit` is enabled, the `ionInput` event will be fired when +the user clears the textarea by performing a keydown event. + */ + ionInput: EventEmitter>; + /** + * Emitted when the input loses focus. + */ + ionBlur: EventEmitter>; + /** + * Emitted when the input has focus. + */ + ionFocus: EventEmitter>; +} diff --git a/packages/angular/standalone/src/directives/toggle.ts b/packages/angular/standalone/src/directives/toggle.ts new file mode 100644 index 00000000000..e1ece5f856e --- /dev/null +++ b/packages/angular/standalone/src/directives/toggle.ts @@ -0,0 +1,84 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + HostListener, + Injector, + NgZone, +} from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ValueAccessor, setIonicClasses } from '@ionic/angular/common'; +import type { ToggleChangeEventDetail, Components } from '@ionic/core/components'; +import { defineCustomElement } from '@ionic/core/components/ion-toggle.js'; + +import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; + +const TOGGLE_INPUTS = [ + 'checked', + 'color', + 'disabled', + 'enableOnOffLabels', + 'justify', + 'labelPlacement', + 'legacy', + 'mode', + 'name', + 'value', +]; + +@ProxyCmp({ + defineCustomElementFn: defineCustomElement, + inputs: TOGGLE_INPUTS, +}) +@Component({ + selector: 'ion-toggle', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: TOGGLE_INPUTS, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: IonToggle, + multi: true, + }, + ], + standalone: true, +}) +export class IonToggle extends ValueAccessor { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) { + super(injector, r); + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionChange', 'ionFocus', 'ionBlur']); + } + + writeValue(value: boolean): void { + this.elementRef.nativeElement.checked = this.lastValue = value; + setIonicClasses(this.elementRef); + } + + @HostListener('ionChange', ['$event.target']) + handleIonChange(el: HTMLIonToggleElement): void { + this.handleValueChange(el, el.checked); + } +} + +export declare interface IonToggle extends Components.IonToggle { + /** + * Emitted when the user switches the toggle on or off. Does not emit +when programmatically changing the value of the `checked` property. + */ + ionChange: EventEmitter>; + /** + * Emitted when the toggle has focus. + */ + ionFocus: EventEmitter>; + /** + * Emitted when the toggle loses focus. + */ + ionBlur: EventEmitter>; +} diff --git a/packages/angular/standalone/src/index.ts b/packages/angular/standalone/src/index.ts index 15895b0fef0..209fa25d777 100644 --- a/packages/angular/standalone/src/index.ts +++ b/packages/angular/standalone/src/index.ts @@ -24,7 +24,20 @@ export { IonicRouteStrategy, } from '@ionic/angular/common'; export { IonNav } from './navigation/nav'; -export { IonIcon } from './directives/icon'; +export { + IonCheckbox, + IonDatetime, + IonInput, + IonIcon, + IonRadioGroup, + IonRadio, + IonRange, + IonSearchbar, + IonSegment, + IonSelect, + IonTextarea, + IonToggle, +} from './directives'; export * from './directives/proxies'; export { diff --git a/packages/angular/test/base/e2e/src/standalone/value-accessors.spec.ts b/packages/angular/test/base/e2e/src/standalone/value-accessors.spec.ts new file mode 100644 index 00000000000..a8e1c36e90c --- /dev/null +++ b/packages/angular/test/base/e2e/src/standalone/value-accessors.spec.ts @@ -0,0 +1,132 @@ + +describe('Value Accessors', () => { + + describe('Checkbox', () => { + beforeEach(() => cy.visit('/standalone/value-accessors/checkbox')); + + it('should update the form value', () => { + cy.get('#formValue').should('have.text', JSON.stringify({ checkbox: false }, null, 2)); + cy.get('ion-checkbox').should('have.class', 'ion-pristine'); + + cy.get('ion-checkbox').click(); + + cy.get('#formValue').should('have.text', JSON.stringify({ checkbox: true }, null, 2)); + cy.get('ion-checkbox').should('have.class', 'ion-dirty'); + cy.get('ion-checkbox').should('have.class', 'ion-valid'); + }); + }); + + describe('Input', () => { + beforeEach(() => cy.visit('/standalone/value-accessors/input')); + + it('should update the form value', () => { + cy.get('#formValue').should('have.text', JSON.stringify({ + inputString: '', + inputNumber: '' + }, null, 2)); + cy.get('ion-input[formControlName="inputString"]').should('have.class', 'ion-pristine'); + cy.get('ion-input[formControlName="inputNumber"]').should('have.class', 'ion-pristine'); + + cy.get('ion-input[formControlName="inputString"]').should('have.class', 'ion-invalid'); + cy.get('ion-input[formControlName="inputNumber"]').should('have.class', 'ion-invalid'); + + cy.get('ion-input[formControlName="inputString"] input').type('test'); + cy.get('ion-input[formControlName="inputString"] input').blur(); + + cy.get('ion-input[formControlName="inputNumber"] input').type(1); + cy.get('ion-input[formControlName="inputNumber"] input').blur(); + + cy.get('#formValue').should('have.text', JSON.stringify({ + inputString: 'test', + inputNumber: 1 + }, null, 2)); + + cy.get('ion-input[formControlName="inputString"]').should('have.class', 'ion-dirty'); + cy.get('ion-input[formControlName="inputNumber"]').should('have.class', 'ion-dirty'); + + cy.get('ion-input[formControlName="inputString"]').should('have.class', 'ion-valid'); + cy.get('ion-input[formControlName="inputNumber"]').should('have.class', 'ion-valid'); + + }); + }); + + describe('Radio Group', () => { + beforeEach(() => cy.visit('/standalone/value-accessors/radio-group')); + + it('should update the form value', () => { + cy.get('#formValue').should('have.text', JSON.stringify({ radioGroup: '1' }, null, 2)); + cy.get('ion-radio-group').should('have.class', 'ion-pristine'); + + cy.get('ion-radio').contains('Two').click(); + + cy.get('#formValue').should('have.text', JSON.stringify({ radioGroup: '2' }, null, 2)); + cy.get('ion-radio-group').should('have.class', 'ion-dirty'); + cy.get('ion-radio-group').should('have.class', 'ion-valid'); + }); + }); + + describe('Searchbar', () => { + beforeEach(() => cy.visit('/standalone/value-accessors/searchbar')); + + it('should update the form value', () => { + cy.get('#formValue').should('have.text', JSON.stringify({ searchbar: '' }, null, 2)); + cy.get('ion-searchbar').should('have.class', 'ion-pristine'); + cy.get('ion-searchbar').should('have.class', 'ion-invalid'); + + cy.get('ion-searchbar').type('test'); + cy.get('ion-searchbar input').blur(); + + cy.get('#formValue').should('have.text', JSON.stringify({ searchbar: 'test' }, null, 2)); + cy.get('ion-searchbar').should('have.class', 'ion-dirty'); + cy.get('ion-searchbar').should('have.class', 'ion-valid'); + }); + }); + + describe('Segment', () => { + beforeEach(() => cy.visit('/standalone/value-accessors/segment')); + + it('should update the form value', () => { + cy.get('#formValue').should('have.text', JSON.stringify({ segment: 'Paid' }, null, 2)); + cy.get('ion-segment').should('have.class', 'ion-pristine'); + + cy.get('ion-segment-button').eq(1).click(); + + cy.get('#formValue').should('have.text', JSON.stringify({ segment: 'Free' }, null, 2)); + cy.get('ion-segment').should('have.class', 'ion-dirty'); + cy.get('ion-segment').should('have.class', 'ion-valid'); + }); + }); + + describe('Textarea', () => { + beforeEach(() => cy.visit('/standalone/value-accessors/textarea')); + + it('should update the form value', () => { + cy.get('#formValue').should('have.text', JSON.stringify({ textarea: '' }, null, 2)); + cy.get('ion-textarea').should('have.class', 'ion-pristine'); + cy.get('ion-textarea').should('have.class', 'ion-invalid'); + + cy.get('ion-textarea').click(); + cy.get('ion-textarea').type('test'); + + cy.get('#formValue').should('have.text', JSON.stringify({ textarea: 'test' }, null, 2)); + cy.get('ion-textarea').should('have.class', 'ion-dirty'); + cy.get('ion-textarea').should('have.class', 'ion-valid'); + }); + }); + + describe('Toggle', () => { + beforeEach(() => cy.visit('/standalone/value-accessors/toggle')); + + it('should update the form value', () => { + cy.get('#formValue').should('have.text', JSON.stringify({ toggle: false }, null, 2)); + cy.get('ion-toggle').should('have.class', 'ion-pristine'); + + cy.get('ion-toggle').click(); + + cy.get('#formValue').should('have.text', JSON.stringify({ toggle: true }, null, 2)); + cy.get('ion-toggle').should('have.class', 'ion-dirty'); + cy.get('ion-toggle').should('have.class', 'ion-valid'); + }); + }); + +}); diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts index d97d34c66bb..93e869651aa 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts @@ -26,6 +26,21 @@ export const routes: Routes = [ { path: 'tab-three', loadComponent: () => import('../tabs/tab3.component').then(c => c.TabThreeComponent) } ] }, + { + path: 'value-accessors', + children: [ + { path: 'checkbox', loadComponent: () => import('../value-accessors/checkbox/checkbox.component').then(c => c.CheckboxComponent) }, + { path: 'datetime', loadComponent: () => import('../value-accessors/datetime/datetime.component').then(c => c.DatetimeComponent) }, + { path: 'input', loadComponent: () => import('../value-accessors/input/input.component').then(c => c.InputComponent) }, + { path: 'radio-group', loadComponent: () => import('../value-accessors/radio-group/radio-group.component').then(c => c.RadioGroupComponent) }, + { path: 'range', loadComponent: () => import('../value-accessors/range/range.component').then(c => c.RangeComponent) }, + { path: 'searchbar', loadComponent: () => import('../value-accessors/searchbar/searchbar.component').then(c => c.SearchbarComponent) }, + { path: 'segment', loadComponent: () => import('../value-accessors/segment/segment.component').then(c => c.SegmentComponent) }, + { path: 'textarea', loadComponent: () => import('../value-accessors/textarea/textarea.component').then(c => c.TextareaComponent) }, + { path: 'toggle', loadComponent: () => import('../value-accessors/toggle/toggle.component').then(c => c.ToggleComponent) }, + { path: '**', redirectTo: 'checkbox' } + ] + } ] }, ]; diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/checkbox/checkbox.component.html b/packages/angular/test/base/src/app/standalone/value-accessors/checkbox/checkbox.component.html new file mode 100644 index 00000000000..2921e06b917 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/checkbox/checkbox.component.html @@ -0,0 +1,11 @@ +
+

IonCheckbox Value Accessors

+

+ This test checks the form integrations with ion-checkbox to make sure values are correctly assigned to the form + group. +

+ + + + +
diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/checkbox/checkbox.component.ts b/packages/angular/test/base/src/app/standalone/value-accessors/checkbox/checkbox.component.ts new file mode 100644 index 00000000000..d58672d9549 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/checkbox/checkbox.component.ts @@ -0,0 +1,25 @@ +import { Component } from "@angular/core"; +import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; +import { IonCheckbox } from "@ionic/angular/standalone"; +import { ValueAccessorTestComponent } from "../value-accessor-test/value-accessor-test.component"; + +@Component({ + selector: 'app-checkbox', + templateUrl: 'checkbox.component.html', + standalone: true, + imports: [ + IonCheckbox, + ReactiveFormsModule, + FormsModule, + ValueAccessorTestComponent + ] +}) +export class CheckboxComponent { + + form = this.fb.group({ + checkbox: [false, Validators.required], + }); + + constructor(private fb: FormBuilder) { } + +} diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/datetime/datetime.component.html b/packages/angular/test/base/src/app/standalone/value-accessors/datetime/datetime.component.html new file mode 100644 index 00000000000..22c36339717 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/datetime/datetime.component.html @@ -0,0 +1,11 @@ +
+

IonDatetime Value Accessors

+

+ This test checks the form integrations with ion-datetime to make sure values are correctly assigned to the form + group. +

+ + + + +
diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/datetime/datetime.component.ts b/packages/angular/test/base/src/app/standalone/value-accessors/datetime/datetime.component.ts new file mode 100644 index 00000000000..7d647e7e032 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/datetime/datetime.component.ts @@ -0,0 +1,25 @@ +import { Component } from "@angular/core"; +import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; +import { IonDatetime } from "@ionic/angular/standalone"; +import { ValueAccessorTestComponent } from "../value-accessor-test/value-accessor-test.component"; + +@Component({ + selector: 'app-datetime', + templateUrl: 'datetime.component.html', + standalone: true, + imports: [ + IonDatetime, + ReactiveFormsModule, + FormsModule, + ValueAccessorTestComponent + ] +}) +export class DatetimeComponent { + + form = this.fb.group({ + datetime: ['2023-05-10T04:00:00', Validators.required], + }); + + constructor(private fb: FormBuilder) { } + +} diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/input/input.component.html b/packages/angular/test/base/src/app/standalone/value-accessors/input/input.component.html new file mode 100644 index 00000000000..e63235cea5a --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/input/input.component.html @@ -0,0 +1,11 @@ +
+

IonInput Value Accessors

+

+ This test checks the form integrations with ion-input to make sure values are correctly assigned to the form group. +

+ + + + + +
diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/input/input.component.ts b/packages/angular/test/base/src/app/standalone/value-accessors/input/input.component.ts new file mode 100644 index 00000000000..1afa4a3b015 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/input/input.component.ts @@ -0,0 +1,26 @@ +import { Component } from "@angular/core"; +import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; +import { IonInput } from "@ionic/angular/standalone"; +import { ValueAccessorTestComponent } from "../value-accessor-test/value-accessor-test.component"; + +@Component({ + selector: 'app-input', + templateUrl: 'input.component.html', + standalone: true, + imports: [ + IonInput, + ReactiveFormsModule, + FormsModule, + ValueAccessorTestComponent, + ] +}) +export class InputComponent { + + form = this.fb.group({ + inputString: ['', Validators.required], + inputNumber: ['', Validators.required], + }); + + constructor(private fb: FormBuilder) { } + +} diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/radio-group/radio-group.component.html b/packages/angular/test/base/src/app/standalone/value-accessors/radio-group/radio-group.component.html new file mode 100644 index 00000000000..a511dbbbe50 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/radio-group/radio-group.component.html @@ -0,0 +1,14 @@ +
+

IonRadioGroup Value Accessors

+

+ This test checks the form integrations with ion-radio-group to make sure values are correctly assigned to the form + group. +

+ + + + One + Two + + +
diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/radio-group/radio-group.component.ts b/packages/angular/test/base/src/app/standalone/value-accessors/radio-group/radio-group.component.ts new file mode 100644 index 00000000000..b4890240c5c --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/radio-group/radio-group.component.ts @@ -0,0 +1,26 @@ +import { Component } from "@angular/core"; +import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; +import { IonRadioGroup, IonRadio } from "@ionic/angular/standalone"; +import { ValueAccessorTestComponent } from "../value-accessor-test/value-accessor-test.component"; + +@Component({ + selector: 'app-radio-group', + templateUrl: 'radio-group.component.html', + standalone: true, + imports: [ + IonRadioGroup, + IonRadio, + ReactiveFormsModule, + FormsModule, + ValueAccessorTestComponent + ] +}) +export class RadioGroupComponent { + + form = this.fb.group({ + radioGroup: ['1', Validators.required], + }); + + constructor(private fb: FormBuilder) { } + +} diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/range/range.component.html b/packages/angular/test/base/src/app/standalone/value-accessors/range/range.component.html new file mode 100644 index 00000000000..9a496fbcd3c --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/range/range.component.html @@ -0,0 +1,9 @@ +
+

IonRange Value Accessors

+

+ This test checks the form integrations with ion-range to make sure values are correctly assigned to the form group. +

+ + + +
diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/range/range.component.ts b/packages/angular/test/base/src/app/standalone/value-accessors/range/range.component.ts new file mode 100644 index 00000000000..f303826524f --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/range/range.component.ts @@ -0,0 +1,25 @@ +import { Component } from "@angular/core"; +import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; +import { IonRange } from "@ionic/angular/standalone"; +import { ValueAccessorTestComponent } from "../value-accessor-test/value-accessor-test.component"; + +@Component({ + selector: 'app-range', + templateUrl: 'range.component.html', + standalone: true, + imports: [ + IonRange, + ReactiveFormsModule, + FormsModule, + ValueAccessorTestComponent + ] +}) +export class RangeComponent { + + form = this.fb.group({ + range: [0, Validators.required], + }); + + constructor(private fb: FormBuilder) { } + +} diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/searchbar/searchbar.component.html b/packages/angular/test/base/src/app/standalone/value-accessors/searchbar/searchbar.component.html new file mode 100644 index 00000000000..9a6137c65ba --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/searchbar/searchbar.component.html @@ -0,0 +1,10 @@ +
+

IonSearchbar Value Accessors

+

+ This test checks the form integrations with ion-searchbar to make sure values are correctly assigned to the form + group. +

+ + + +
diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/searchbar/searchbar.component.ts b/packages/angular/test/base/src/app/standalone/value-accessors/searchbar/searchbar.component.ts new file mode 100644 index 00000000000..6113ed5ddcb --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/searchbar/searchbar.component.ts @@ -0,0 +1,25 @@ +import { Component } from "@angular/core"; +import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; +import { IonSearchbar } from "@ionic/angular/standalone"; +import { ValueAccessorTestComponent } from "../value-accessor-test/value-accessor-test.component"; + +@Component({ + selector: 'app-searchbar', + templateUrl: 'searchbar.component.html', + standalone: true, + imports: [ + IonSearchbar, + ReactiveFormsModule, + FormsModule, + ValueAccessorTestComponent + ] +}) +export class SearchbarComponent { + + form = this.fb.group({ + searchbar: ['', Validators.required], + }); + + constructor(private fb: FormBuilder) { } + +} diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/segment/segment.component.html b/packages/angular/test/base/src/app/standalone/value-accessors/segment/segment.component.html new file mode 100644 index 00000000000..98016fcde68 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/segment/segment.component.html @@ -0,0 +1,18 @@ +
+

IonSegment Value Accessors

+

+ This test checks the form integrations with ion-segment to make sure values are correctly assigned to the form + group. +

+ + + + + Paid + + + Free + + + +
diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/segment/segment.component.ts b/packages/angular/test/base/src/app/standalone/value-accessors/segment/segment.component.ts new file mode 100644 index 00000000000..d3de204881a --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/segment/segment.component.ts @@ -0,0 +1,27 @@ +import { Component } from "@angular/core"; +import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; +import { IonSegment, IonSegmentButton, IonLabel } from "@ionic/angular/standalone"; +import { ValueAccessorTestComponent } from "../value-accessor-test/value-accessor-test.component"; + +@Component({ + selector: 'app-segment', + templateUrl: 'segment.component.html', + standalone: true, + imports: [ + IonSegment, + IonSegmentButton, + IonLabel, + ReactiveFormsModule, + FormsModule, + ValueAccessorTestComponent + ] +}) +export class SegmentComponent { + + form = this.fb.group({ + segment: ['Paid', Validators.required], + }); + + constructor(private fb: FormBuilder) { } + +} diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/textarea/textarea.component.html b/packages/angular/test/base/src/app/standalone/value-accessors/textarea/textarea.component.html new file mode 100644 index 00000000000..678d42d391a --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/textarea/textarea.component.html @@ -0,0 +1,11 @@ +
+

IonTextarea Value Accessors

+

+ This test checks the form integrations with ion-textarea to make sure values are correctly assigned to the form + group. +

+ + + + +
diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/textarea/textarea.component.ts b/packages/angular/test/base/src/app/standalone/value-accessors/textarea/textarea.component.ts new file mode 100644 index 00000000000..9410c03dceb --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/textarea/textarea.component.ts @@ -0,0 +1,25 @@ +import { Component } from "@angular/core"; +import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; +import { IonTextarea } from "@ionic/angular/standalone"; +import { ValueAccessorTestComponent } from "../value-accessor-test/value-accessor-test.component"; + +@Component({ + selector: 'app-textarea', + templateUrl: 'textarea.component.html', + standalone: true, + imports: [ + IonTextarea, + ReactiveFormsModule, + FormsModule, + ValueAccessorTestComponent + ] +}) +export class TextareaComponent { + + form = this.fb.group({ + textarea: ['', Validators.required], + }); + + constructor(private fb: FormBuilder) { } + +} diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/toggle/toggle.component.html b/packages/angular/test/base/src/app/standalone/value-accessors/toggle/toggle.component.html new file mode 100644 index 00000000000..08888059f99 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/toggle/toggle.component.html @@ -0,0 +1,10 @@ +
+

IonToggle Value Accessors

+

+ This test checks the form integrations with ion-toggle to make sure values are correctly assigned to the form group. +

+ + + + +
diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/toggle/toggle.component.ts b/packages/angular/test/base/src/app/standalone/value-accessors/toggle/toggle.component.ts new file mode 100644 index 00000000000..3b72b4c16dc --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/toggle/toggle.component.ts @@ -0,0 +1,25 @@ +import { Component } from "@angular/core"; +import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; +import { IonToggle } from "@ionic/angular/standalone"; +import { ValueAccessorTestComponent } from "../value-accessor-test/value-accessor-test.component"; + +@Component({ + selector: 'app-toggle', + templateUrl: 'toggle.component.html', + standalone: true, + imports: [ + IonToggle, + ReactiveFormsModule, + FormsModule, + ValueAccessorTestComponent + ] +}) +export class ToggleComponent { + + form = this.fb.group({ + toggle: [false, Validators.required], + }); + + constructor(private fb: FormBuilder) { } + +} diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/value-accessor-test/value-accessor-test.component.html b/packages/angular/test/base/src/app/standalone/value-accessors/value-accessor-test/value-accessor-test.component.html new file mode 100644 index 00000000000..754b47c7994 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/value-accessor-test/value-accessor-test.component.html @@ -0,0 +1,16 @@ +
+
+ +
+ + + +

Form Value

+
{{ formGroup.value | json }}
+ +

Form Validity

+
    +
  • Form valid: {{ formGroup.valid }}
  • +
  • {{ control.key }} valid: {{ control.value.valid }}
  • +
+
diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/value-accessor-test/value-accessor-test.component.ts b/packages/angular/test/base/src/app/standalone/value-accessors/value-accessor-test/value-accessor-test.component.ts new file mode 100644 index 00000000000..6796f645646 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/value-accessors/value-accessor-test/value-accessor-test.component.ts @@ -0,0 +1,27 @@ +import { CommonModule, JsonPipe, KeyValuePipe } from "@angular/common"; +import { Component, Input } from "@angular/core"; +import { FormGroup, FormsModule, ReactiveFormsModule } from "@angular/forms"; + +@Component({ + selector: 'app-value-accessor-test', + templateUrl: 'value-accessor-test.component.html', + standalone: true, + imports: [ + ReactiveFormsModule, + FormsModule, + JsonPipe, + KeyValuePipe, + /** + * NgFor directive is not available until Angular 15. + * We import the CommonModule for now. + * + * TODO: FW-5197 Replace with NgFor when dropping Angular 14 support. + */ + CommonModule + ] +}) +export class ValueAccessorTestComponent { + + @Input() formGroup!: FormGroup; + +} From a6e4fc4feb5259f4444bc02d8c0dac096b29a1a0 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Wed, 4 Oct 2023 14:30:25 -0400 Subject: [PATCH 23/24] fix(angular): routerLink directives can be used (#28282) --- .../src/navigation/router-link-delegate.ts | 12 +++++------- .../router-link/router-link.component.html | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/angular/standalone/src/navigation/router-link-delegate.ts b/packages/angular/standalone/src/navigation/router-link-delegate.ts index 5d4840f300a..3f9ab4a73ad 100644 --- a/packages/angular/standalone/src/navigation/router-link-delegate.ts +++ b/packages/angular/standalone/src/navigation/router-link-delegate.ts @@ -1,21 +1,19 @@ -import { Component } from '@angular/core'; +import { Directive } from '@angular/core'; import { RouterLinkDelegateDirective as RouterLinkDelegateBase, RouterLinkWithHrefDelegateDirective as RouterLinkHrefDelegateBase, } from '@ionic/angular/common'; -@Component({ - // eslint-disable-next-line @angular-eslint/component-selector +@Directive({ selector: ':not(a):not(area)[routerLink]', - template: '', standalone: true, }) +// eslint-disable-next-line @angular-eslint/directive-class-suffix export class IonRouterLink extends RouterLinkDelegateBase {} -@Component({ - // eslint-disable-next-line @angular-eslint/component-selector +@Directive({ selector: 'a[routerLink],area[routerLink]', - template: '', standalone: true, }) +// eslint-disable-next-line @angular-eslint/directive-class-suffix export class IonRouterLinkWithHref extends RouterLinkHrefDelegateBase {} diff --git a/packages/angular/test/base/src/app/standalone/router-link/router-link.component.html b/packages/angular/test/base/src/app/standalone/router-link/router-link.component.html index d06acc30be7..078d4b7c3a4 100644 --- a/packages/angular/test/base/src/app/standalone/router-link/router-link.component.html +++ b/packages/angular/test/base/src/app/standalone/router-link/router-link.component.html @@ -1,6 +1,6 @@ - + I'm a link - From b7273c4613c7a1224625ab90a30ccbcc40ea8b5c Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Tue, 10 Oct 2023 10:42:49 -0400 Subject: [PATCH 24/24] chore: use production build of ionicons in test app --- .../angular/test/apps/ng14/package-lock.json | 37 ++++++-- packages/angular/test/apps/ng14/package.json | 2 +- .../angular/test/apps/ng15/package-lock.json | 37 ++++++-- packages/angular/test/apps/ng15/package.json | 2 +- .../angular/test/apps/ng16/package-lock.json | 87 ++++--------------- packages/angular/test/apps/ng16/package.json | 2 +- 6 files changed, 76 insertions(+), 91 deletions(-) diff --git a/packages/angular/test/apps/ng14/package-lock.json b/packages/angular/test/apps/ng14/package-lock.json index 085ed93efd5..ba2594d5c57 100644 --- a/packages/angular/test/apps/ng14/package-lock.json +++ b/packages/angular/test/apps/ng14/package-lock.json @@ -22,7 +22,7 @@ "@ionic/angular-server": "^6.1.15", "core-js": "^2.6.11", "express": "^4.15.2", - "ionicons": "^7.1.3-dev.11692630068.1f5f09ee", + "ionicons": "^7.2.0", "rxjs": "~7.5.0", "tslib": "^2.3.0", "typescript-eslint-language-service": "^4.1.5", @@ -9963,11 +9963,23 @@ } }, "node_modules/ionicons": { - "version": "7.1.3-dev.11692630068.1f5f09ee", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.3-dev.11692630068.1f5f09ee.tgz", - "integrity": "sha512-UadZRbi50/IFJM5sIa3X7+c1sVsA0AB7uLrLyH7SRTX2MCol7clT1gfw/si28tG0NWScDyvbdC2SgzhN7lkroA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.2.0.tgz", + "integrity": "sha512-gmG6A50iem/wR10K+rJeiYlBu8SjG1lxZ6brPYEI2WYjEw8s6T+bj///C6yjRIDtj+qIgqrQmQAgNvNdgyG7ng==", "dependencies": { - "@stencil/core": "^2.18.0" + "@stencil/core": "^4.0.3" + } + }, + "node_modules/ionicons/node_modules/@stencil/core": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.4.1.tgz", + "integrity": "sha512-SirGcrb5yKHCn2BwdM7HGVXuvCdmwiXlVczEj8jJxQIm42CAUQCUECxtZidTzp+oZBZnWLnoAvfanchJsgkQzA==", + "bin": { + "stencil": "bin/stencil" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.10.0" } }, "node_modules/ip": { @@ -23777,11 +23789,18 @@ "dev": true }, "ionicons": { - "version": "7.1.3-dev.11692630068.1f5f09ee", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.3-dev.11692630068.1f5f09ee.tgz", - "integrity": "sha512-UadZRbi50/IFJM5sIa3X7+c1sVsA0AB7uLrLyH7SRTX2MCol7clT1gfw/si28tG0NWScDyvbdC2SgzhN7lkroA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.2.0.tgz", + "integrity": "sha512-gmG6A50iem/wR10K+rJeiYlBu8SjG1lxZ6brPYEI2WYjEw8s6T+bj///C6yjRIDtj+qIgqrQmQAgNvNdgyG7ng==", "requires": { - "@stencil/core": "^2.18.0" + "@stencil/core": "^4.0.3" + }, + "dependencies": { + "@stencil/core": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.4.1.tgz", + "integrity": "sha512-SirGcrb5yKHCn2BwdM7HGVXuvCdmwiXlVczEj8jJxQIm42CAUQCUECxtZidTzp+oZBZnWLnoAvfanchJsgkQzA==" + } } }, "ip": { diff --git a/packages/angular/test/apps/ng14/package.json b/packages/angular/test/apps/ng14/package.json index 91eef6ac600..3047c7fdedd 100644 --- a/packages/angular/test/apps/ng14/package.json +++ b/packages/angular/test/apps/ng14/package.json @@ -33,7 +33,7 @@ "@ionic/angular-server": "^6.1.15", "core-js": "^2.6.11", "express": "^4.15.2", - "ionicons": "7.1.3-dev.11692630068.1f5f09ee", + "ionicons": "^7.2.0", "rxjs": "~7.5.0", "tslib": "^2.3.0", "typescript-eslint-language-service": "^4.1.5", diff --git a/packages/angular/test/apps/ng15/package-lock.json b/packages/angular/test/apps/ng15/package-lock.json index ac56062110d..70b3feda841 100644 --- a/packages/angular/test/apps/ng15/package-lock.json +++ b/packages/angular/test/apps/ng15/package-lock.json @@ -23,7 +23,7 @@ "@nguniversal/express-engine": "^15.0.0", "core-js": "^2.6.11", "express": "^4.15.2", - "ionicons": "7.1.3-dev.11692630068.1f5f09ee", + "ionicons": "^7.2.0", "rxjs": "~7.5.0", "tslib": "^2.3.0", "typescript-eslint-language-service": "^4.1.5", @@ -9546,11 +9546,23 @@ } }, "node_modules/ionicons": { - "version": "7.1.3-dev.11692630068.1f5f09ee", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.3-dev.11692630068.1f5f09ee.tgz", - "integrity": "sha512-UadZRbi50/IFJM5sIa3X7+c1sVsA0AB7uLrLyH7SRTX2MCol7clT1gfw/si28tG0NWScDyvbdC2SgzhN7lkroA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.2.0.tgz", + "integrity": "sha512-gmG6A50iem/wR10K+rJeiYlBu8SjG1lxZ6brPYEI2WYjEw8s6T+bj///C6yjRIDtj+qIgqrQmQAgNvNdgyG7ng==", "dependencies": { - "@stencil/core": "^2.18.0" + "@stencil/core": "^4.0.3" + } + }, + "node_modules/ionicons/node_modules/@stencil/core": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.4.1.tgz", + "integrity": "sha512-SirGcrb5yKHCn2BwdM7HGVXuvCdmwiXlVczEj8jJxQIm42CAUQCUECxtZidTzp+oZBZnWLnoAvfanchJsgkQzA==", + "bin": { + "stencil": "bin/stencil" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.10.0" } }, "node_modules/ip": { @@ -22642,11 +22654,18 @@ "dev": true }, "ionicons": { - "version": "7.1.3-dev.11692630068.1f5f09ee", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.3-dev.11692630068.1f5f09ee.tgz", - "integrity": "sha512-UadZRbi50/IFJM5sIa3X7+c1sVsA0AB7uLrLyH7SRTX2MCol7clT1gfw/si28tG0NWScDyvbdC2SgzhN7lkroA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.2.0.tgz", + "integrity": "sha512-gmG6A50iem/wR10K+rJeiYlBu8SjG1lxZ6brPYEI2WYjEw8s6T+bj///C6yjRIDtj+qIgqrQmQAgNvNdgyG7ng==", "requires": { - "@stencil/core": "^2.18.0" + "@stencil/core": "^4.0.3" + }, + "dependencies": { + "@stencil/core": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.4.1.tgz", + "integrity": "sha512-SirGcrb5yKHCn2BwdM7HGVXuvCdmwiXlVczEj8jJxQIm42CAUQCUECxtZidTzp+oZBZnWLnoAvfanchJsgkQzA==" + } } }, "ip": { diff --git a/packages/angular/test/apps/ng15/package.json b/packages/angular/test/apps/ng15/package.json index f6f0e88f47b..c263f2d47f1 100644 --- a/packages/angular/test/apps/ng15/package.json +++ b/packages/angular/test/apps/ng15/package.json @@ -34,7 +34,7 @@ "@nguniversal/express-engine": "^15.0.0", "core-js": "^2.6.11", "express": "^4.15.2", - "ionicons": "7.1.3-dev.11692630068.1f5f09ee", + "ionicons": "^7.2.0", "rxjs": "~7.5.0", "tslib": "^2.3.0", "typescript-eslint-language-service": "^4.1.5", diff --git a/packages/angular/test/apps/ng16/package-lock.json b/packages/angular/test/apps/ng16/package-lock.json index 7feda4e53a2..d662c68d769 100644 --- a/packages/angular/test/apps/ng16/package-lock.json +++ b/packages/angular/test/apps/ng16/package-lock.json @@ -22,7 +22,7 @@ "@nguniversal/express-engine": "^16.0.0", "core-js": "^2.6.11", "express": "^4.15.2", - "ionicons": "7.1.3-dev.11692630068.1f5f09ee", + "ionicons": "^7.2.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", "typescript-eslint-language-service": "^4.1.5", @@ -3236,14 +3236,6 @@ "zone.js": ">=0.11.0" } }, - "node_modules/@ionic/angular/node_modules/ionicons": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.2.tgz", - "integrity": "sha512-zZ4njAqSP39H8RRvZhJvkHsv7cBjYE/VfInH218Osf2UVxJITSOutTTd25MW+tAXKN5fheYzclUXUsF55JHUDg==", - "dependencies": { - "@stencil/core": "^2.18.0" - } - }, "node_modules/@ionic/core": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.0.2.tgz", @@ -3266,26 +3258,6 @@ "npm": ">=6.0.0" } }, - "node_modules/@ionic/core/node_modules/ionicons": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.2.tgz", - "integrity": "sha512-zZ4njAqSP39H8RRvZhJvkHsv7cBjYE/VfInH218Osf2UVxJITSOutTTd25MW+tAXKN5fheYzclUXUsF55JHUDg==", - "dependencies": { - "@stencil/core": "^2.18.0" - } - }, - "node_modules/@ionic/core/node_modules/ionicons/node_modules/@stencil/core": { - "version": "2.22.3", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.22.3.tgz", - "integrity": "sha512-kmVA0M/HojwsfkeHsifvHVIYe4l5tin7J5+DLgtl8h6WWfiMClND5K3ifCXXI2ETDNKiEk21p6jql3Fx9o2rng==", - "bin": { - "stencil": "bin/stencil" - }, - "engines": { - "node": ">=12.10.0", - "npm": ">=6.0.0" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -4014,15 +3986,15 @@ "dev": true }, "node_modules/@stencil/core": { - "version": "2.19.2", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.19.2.tgz", - "integrity": "sha512-TK3sHqyQAACwcac4fWntypFtN9c/y+y9ioZojeCnNDY3dxF33Ax70lL9ZLDyQnWWR+aTh1WFoqB5sYOnZIUKMA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.4.1.tgz", + "integrity": "sha512-SirGcrb5yKHCn2BwdM7HGVXuvCdmwiXlVczEj8jJxQIm42CAUQCUECxtZidTzp+oZBZnWLnoAvfanchJsgkQzA==", "bin": { "stencil": "bin/stencil" }, "engines": { - "node": ">=12.10.0", - "npm": ">=6.0.0" + "node": ">=16.0.0", + "npm": ">=7.10.0" } }, "node_modules/@tootallnate/once": { @@ -10154,11 +10126,11 @@ } }, "node_modules/ionicons": { - "version": "7.1.3-dev.11692630068.1f5f09ee", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.3-dev.11692630068.1f5f09ee.tgz", - "integrity": "sha512-UadZRbi50/IFJM5sIa3X7+c1sVsA0AB7uLrLyH7SRTX2MCol7clT1gfw/si28tG0NWScDyvbdC2SgzhN7lkroA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.2.0.tgz", + "integrity": "sha512-gmG6A50iem/wR10K+rJeiYlBu8SjG1lxZ6brPYEI2WYjEw8s6T+bj///C6yjRIDtj+qIgqrQmQAgNvNdgyG7ng==", "dependencies": { - "@stencil/core": "^2.18.0" + "@stencil/core": "^4.0.3" } }, "node_modules/ip": { @@ -19169,16 +19141,6 @@ "ionicons": "^7.0.0", "jsonc-parser": "^3.0.0", "tslib": "^2.3.0" - }, - "dependencies": { - "ionicons": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.2.tgz", - "integrity": "sha512-zZ4njAqSP39H8RRvZhJvkHsv7cBjYE/VfInH218Osf2UVxJITSOutTTd25MW+tAXKN5fheYzclUXUsF55JHUDg==", - "requires": { - "@stencil/core": "^2.18.0" - } - } } }, "@ionic/angular-server": { @@ -19204,21 +19166,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.2.1.tgz", "integrity": "sha512-Ybm4NteQBScLq3H0JML/uqo4nWjNpZw1HAAURtR5LlRm7ptzNKO5S8EnHp3m05/uyTzeh9yLpUFHY7bxGNdYLg==" - }, - "ionicons": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.2.tgz", - "integrity": "sha512-zZ4njAqSP39H8RRvZhJvkHsv7cBjYE/VfInH218Osf2UVxJITSOutTTd25MW+tAXKN5fheYzclUXUsF55JHUDg==", - "requires": { - "@stencil/core": "^2.18.0" - }, - "dependencies": { - "@stencil/core": { - "version": "2.22.3", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.22.3.tgz", - "integrity": "sha512-kmVA0M/HojwsfkeHsifvHVIYe4l5tin7J5+DLgtl8h6WWfiMClND5K3ifCXXI2ETDNKiEk21p6jql3Fx9o2rng==" - } - } } } }, @@ -19717,9 +19664,9 @@ "dev": true }, "@stencil/core": { - "version": "2.19.2", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.19.2.tgz", - "integrity": "sha512-TK3sHqyQAACwcac4fWntypFtN9c/y+y9ioZojeCnNDY3dxF33Ax70lL9ZLDyQnWWR+aTh1WFoqB5sYOnZIUKMA==" + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.4.1.tgz", + "integrity": "sha512-SirGcrb5yKHCn2BwdM7HGVXuvCdmwiXlVczEj8jJxQIm42CAUQCUECxtZidTzp+oZBZnWLnoAvfanchJsgkQzA==" }, "@tootallnate/once": { "version": "2.0.0", @@ -24335,11 +24282,11 @@ "dev": true }, "ionicons": { - "version": "7.1.3-dev.11692630068.1f5f09ee", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.3-dev.11692630068.1f5f09ee.tgz", - "integrity": "sha512-UadZRbi50/IFJM5sIa3X7+c1sVsA0AB7uLrLyH7SRTX2MCol7clT1gfw/si28tG0NWScDyvbdC2SgzhN7lkroA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.2.0.tgz", + "integrity": "sha512-gmG6A50iem/wR10K+rJeiYlBu8SjG1lxZ6brPYEI2WYjEw8s6T+bj///C6yjRIDtj+qIgqrQmQAgNvNdgyG7ng==", "requires": { - "@stencil/core": "^2.18.0" + "@stencil/core": "^4.0.3" } }, "ip": { diff --git a/packages/angular/test/apps/ng16/package.json b/packages/angular/test/apps/ng16/package.json index f5e4ee2907d..d12d21419a0 100644 --- a/packages/angular/test/apps/ng16/package.json +++ b/packages/angular/test/apps/ng16/package.json @@ -33,7 +33,7 @@ "@nguniversal/express-engine": "^16.0.0", "core-js": "^2.6.11", "express": "^4.15.2", - "ionicons": "7.1.3-dev.11692630068.1f5f09ee", + "ionicons": "^7.2.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", "typescript-eslint-language-service": "^4.1.5",