diff --git a/angular/src/index.ts b/angular/src/index.ts index a77a85614a..c116ef2076 100644 --- a/angular/src/index.ts +++ b/angular/src/index.ts @@ -43,7 +43,7 @@ export * from './types/ionic-lifecycle-hooks'; export { IonicModule } from './ionic-module'; // UTILS -export { IonicSafeString, getPlatforms, isPlatform, createAnimation } from '@ionic/core'; +export { IonicSafeString, getPlatforms, isPlatform, createAnimation, IonicSwiper } from '@ionic/core'; // CORE TYPES export { diff --git a/core/src/components/slides/IonicSwiper.ts b/core/src/components/slides/IonicSwiper.ts new file mode 100644 index 0000000000..da107b5af1 --- /dev/null +++ b/core/src/components/slides/IonicSwiper.ts @@ -0,0 +1,126 @@ +import { addEventListener, raf, removeEventListener } from '../../utils/helpers'; + +/** + * This is a plugin for Swiper that allows it to work + * with Ionic Framework and the routing integrations. + * Without this plugin, Swiper would be incapable of correctly + * determining the dimensions of the slides component as + * each view is initially hidden before transitioning in. + */ +const setupSwiperInIonic = (swiper: any, watchForIonPageChanges = true) => { + if (typeof (window as any) === 'undefined') { return; } + + const swiperEl = swiper.el; + const ionPage = swiperEl.closest('.ion-page'); + + if (!ionPage) { + if (watchForIonPageChanges) { + + /** + * If no ion page found, it is possible + * that we are in the overlay setup step + * where the inner component has been + * created but not attached to the DOM yet. + * If so, wait for the .ion-page class to + * appear on the root div and re-run setup. + */ + const rootNode = swiperEl.getRootNode(); + if (rootNode.tagName === 'DIV') { + const mo = new MutationObserver((m: MutationRecord[]) => { + const mutation = m[0]; + const wasEmpty = mutation.oldValue === null; + const hasIonPage = rootNode.classList.contains('ion-page'); + + /** + * Now that we have an .ion-page class + * we can safely attempt setup again. + */ + if (wasEmpty && hasIonPage) { + mo.disconnect(); + + /** + * Set false here so we do not + * get infinite loops + */ + setupSwiperInIonic(swiper, false); + } + }); + + mo.observe(rootNode, { + attributeFilter: ['class'], + attributeOldValue: true + }); + } + } + return; + } + + /** + * If using slides in a modal or + * popover we need to wait for the + * overlay to be shown as these components + * are hidden when they are initially created. + */ + const modalOrPopover = swiperEl.closest('ion-modal, ion-popover'); + if (modalOrPopover) { + const eventName = modalOrPopover.tagName === 'ION-MODAL' ? 'ionModalWillPresent' : 'ionPopoverWillPresent'; + const overlayCallback = () => { + /** + * We need an raf here so the update + * is fired one tick after the overlay is shown. + */ + raf(() => { + swiperEl.swiper.update(); + removeEventListener(modalOrPopover, eventName, overlayCallback); + }); + } + addEventListener(modalOrPopover, eventName, overlayCallback); + } else { + /** + * If using slides in a page + * we need to wait for the ion-page-invisible + * class to be removed so Swiper can correctly + * compute the dimensions of the slides. + */ + const mo = new MutationObserver((m: MutationRecord[]) => { + const mutation = m[0]; + const wasPageHidden = mutation.oldValue?.includes('ion-page-invisible'); + const isPageHidden = ionPage.classList.contains('ion-page-invisible'); + + /** + * Only update Swiper if the page was + * hidden but is no longer hidden. + */ + if (!isPageHidden && isPageHidden !== wasPageHidden) { + swiperEl.swiper.update(); + } + }); + + mo.observe(ionPage, { + attributeFilter: ['class'], + attributeOldValue: true + }); + } + + /** + * We also need to listen for the appload event + * which is emitted by Stencil in the + * event that Swiper is being used on the + * view that is rendered initially. + */ + const onAppLoad = () => { + swiperEl.swiper.update(); + removeEventListener(window, 'appload', onAppLoad); + } + + addEventListener(window, 'appload', onAppLoad); +} + +export const IonicSwiper = { + name: 'ionic', + on: { + afterInit(swiper: any) { + setupSwiperInIonic(swiper); + } + } +} diff --git a/core/src/components/slides/readme.md b/core/src/components/slides/readme.md index 6ab54c5620..54fcaae015 100644 --- a/core/src/components/slides/readme.md +++ b/core/src/components/slides/readme.md @@ -3,6 +3,7 @@ The Slides component is a multi-section container. Each section can be swiped or dragged between. It contains any number of [Slide](../slide) components. +This guide will cover migration from the deprecated `ion-slides` component to the framework-specific solutions that Swiper.js provides as well as the existing `ion-slides` API for developers who are still using that component. Adopted from Swiper.js: The most modern mobile touch slider and framework with hardware accelerated transitions. @@ -15,6 +16,42 @@ http://www.idangero.us/ Licensed under MIT +## Migration + +With the release of Ionic Framework v6, the Ionic Team has deprecated the `ion-slides` and `ion-slide` components in favor of using the official framework integrations provided by Swiper. Fear not! You will still be able to use slides components in your application. Additionally, because you are still using Swiper, the functionality of your slides component should remain exactly the same. + +### What is Swiper.js? + +Swiper.js is the carousel/slider library that powers `ion-slides`. The library is bundled automatically with all versions of Ionic Framework. When Ionic Framework v4. was first released, Swiper did not have framework specific integrations of its library, so `ion-slides` was created as a way of bridging the gap between the core Swiper library and frameworks such as Angular, React, and Vue. + +Since then, the Swiper team has released framework specific integrations of Swiper.js for Angular, React, Vue, and more! + +### What are the benefits of this change? + +There are several benefits for members of the Ionic Framework community. By using the official Swiper.js framework integrations: + +- Developers can now be in control of the exact version of Swiper.js they want to use. Previously, developers would need to rely on the Ionic Team to update the version internally and release a new version of Ionic Framework. +- The Ionic Team can spend more time triaging and fixing other non-slides issues, speeding up our development process so we can make the framework work better for our community. +- Developers should experience fewer bugs. +- Application bundle sizes can shrink in some cases. By installing Swiper.js as a 3rd party dependency in your application, bundlers such as Webpack or Rollup should be able to treeshake your code better. +- Developers have access to new features that they previously did not have when using `ion-slides`. + +### How long do I have to migrate? + +We plan to remove `ion-slides` and `ion-slide` in Ionic Framework v7. `ion-slides` and `ion-slide` will continue to be available for the entire Ionic Framework v6 lifecycle but will only receive critical bug fixes. + +### How do I migrate? + +Since the underlying technology that powers your slides is the same, the migration process is easy! Follow the guides below for your specific framework. + +Migration for Ionic Angular users: https://ionicframework.com/docs/angular/slides +Migration for Ionic React users: https://ionicframework.com/docs/react/slides +Migration for Ionic Vue users: https://ionicframework.com/docs/vue/slides + +------ + +The following documentation applies to the `ion-slides` component. + ## Custom Animations By default, Ionic slides use the built-in `slide` animation effect. Custom animations can be provided via the `options` property. Examples of other animations can be found below. diff --git a/core/src/components/slides/slides.tsx b/core/src/components/slides/slides.tsx index 06b88f1df6..fb834a16ae 100644 --- a/core/src/components/slides/slides.tsx +++ b/core/src/components/slides/slides.tsx @@ -136,6 +136,10 @@ export class Slides implements ComponentInterface { */ @Event() ionSlideTouchEnd!: EventEmitter; + componentWillLoad() { + console.warn(`[Deprecation Warning]: ion-slides has been deprecated and will be removed in Ionic Framework v7.0. We recommend using the framework-specific integrations that Swiper.js provides, allowing for faster bug fixes and an improved developer experience. See https://ionicframework.com/docs/api/slides#migration for more information including migration steps.`); + } + connectedCallback() { // tslint:disable-next-line: strict-type-predicates if (typeof MutationObserver !== 'undefined') { diff --git a/core/src/css/ionic-swiper.scss b/core/src/css/ionic-swiper.scss new file mode 100644 index 0000000000..8ebba40c5b --- /dev/null +++ b/core/src/css/ionic-swiper.scss @@ -0,0 +1,109 @@ +@import "../themes/ionic.skip-warns.scss"; +@import "../components/slides/slides.ios.vars.scss"; + +// Slides +// -------------------------------------------------- + +.swiper-container { + + // These values are the same for iOS and MD + // We just do not add a .md or .ios class beforehand + // so the styles are easier to override by the user. + --bullet-background: #{$slides-ios-bullet-background}; + --bullet-background-active: #{$slides-ios-bullet-background-active}; + --progress-bar-background: #{$slides-ios-progress-bar-background}; + --progress-bar-background-active: #{$slides-ios-progress-bar-background-active}; + --scroll-bar-background: #{$slides-ios-scroll-bar-background}; + --scroll-bar-background-active: #{$slides-ios-scroll-bar-drag-background}; + /** + * @prop --bullet-background: Background of the pagination bullets + * @prop --bullet-background-active: Background of the active pagination bullet + * + * @prop --progress-bar-background: Background of the pagination progress-bar + * @prop --progress-bar-background-active: Background of the active pagination progress-bar + * + * @prop --scroll-bar-background: Background of the pagination scroll-bar + * @prop --scroll-bar-background-active: Background of the active pagination scroll-bar + */ + display: block; + + user-select: none; +} + +// Pagination Bullets +// -------------------------------------------------- + +.swiper-pagination-bullet { + background: var(--bullet-background); +} + +.swiper-pagination-bullet-active { + background: var(--bullet-background-active); +} + + +// Pagination Progress Bar +// -------------------------------------------------- + +.swiper-pagination-progressbar { + background: var(--progress-bar-background); +} + +.swiper-pagination-progressbar .swiper-pagination-progressbar-fill { + background: var(--progress-bar-background-active); +} + +// Scrollbar +// -------------------------------------------------- + +.swiper-scrollbar { + background: var(--scroll-bar-background); +} + +.swiper-scrollbar-drag { + background: var(--scroll-bar-background-active); +} + +// Slide +// -------------------------------------------------- + +ion-slide { + display: block; + + width: 100%; + height: 100%; +} + +.slide-zoom { + display: block; + + width: 100%; + + text-align: center; +} + +.swiper-slide { + + // Center slide text vertically + display: flex; + position: relative; + + flex-shrink: 0; + align-items: center; + justify-content: center; + + width: 100%; + height: 100%; + + font-size: 18px; + + text-align: center; + box-sizing: border-box; +} + +.swiper-slide img { + width: auto; + max-width: 100%; + height: auto; + max-height: 100%; +} diff --git a/core/src/index.ts b/core/src/index.ts index e833077ab2..f493a61802 100644 --- a/core/src/index.ts +++ b/core/src/index.ts @@ -13,3 +13,4 @@ export { IonicConfig, getMode, setupConfig } from './utils/config'; export { LIFECYCLE_WILL_ENTER, LIFECYCLE_DID_ENTER, LIFECYCLE_WILL_LEAVE, LIFECYCLE_DID_LEAVE, LIFECYCLE_WILL_UNLOAD } from './components/nav/constants'; export { menuController } from './utils/menu-controller'; export { alertController, actionSheetController, modalController, loadingController, pickerController, popoverController, toastController } from './utils/overlays'; +export { IonicSwiper } from './components/slides/IonicSwiper'; diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index 808f80ac88..0d76ff637a 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -33,6 +33,7 @@ export { mdTransitionAnimation, NavComponentWithProps, setupConfig, + IonicSwiper, SpinnerTypes, diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 1dd6a55697..baa0d7cf0c 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -72,6 +72,9 @@ export { // Hardware Back Button BackButtonEvent, + // Swiper + IonicSwiper, + SpinnerTypes, ActionSheetOptions,